=== modified file 'includes/form.inc' --- includes/form.inc 2009-01-12 07:00:09 +0000 +++ includes/form.inc 2009-01-15 16:50:15 +0000 @@ -920,8 +920,10 @@ // after normal input parsing has been completed. if (isset($form['#after_build']) && !isset($form['#after_build_done'])) { foreach ($form['#after_build'] as $function) { - $form = $function($form, $form_state); - $form['#after_build_done'] = TRUE; + if (drupal_function_exists($function)) { + $form = $function($form, $form_state); + $form['#after_build_done'] = TRUE; + } } } === modified file 'includes/install.inc' --- includes/install.inc 2009-01-04 19:00:08 +0000 +++ includes/install.inc 2009-01-15 16:50:16 +0000 @@ -490,8 +490,10 @@ * * @param $module_list * The modules to install. + * @param $disable_modules_installed_hook + * Normally just testing wants to set this to TRUE. */ -function drupal_install_modules($module_list = array()) { +function drupal_install_modules($module_list = array(), $disable_modules_installed_hook = FALSE) { $files = module_rebuild_cache(); $module_list = array_flip(array_values($module_list)); do { @@ -511,7 +513,7 @@ asort($module_list); $module_list = array_keys($module_list); $modules_installed = array_filter($module_list, '_drupal_install_module'); - if (!empty($modules_installed)) { + if (!$disable_modules_installed_hook && !empty($modules_installed)) { module_invoke_all('modules_installed', $modules_installed); } module_enable($module_list); === modified file 'includes/module.inc' --- includes/module.inc 2009-01-14 13:00:07 +0000 +++ includes/module.inc 2009-01-15 16:50:53 +0000 @@ -267,7 +267,7 @@ // Force to regenerate the stored list of hook implementations. registry_rebuild(); } - + foreach ($invoke_modules as $module) { module_invoke($module, 'enable'); // Check if node_access table needs rebuilding. @@ -427,7 +427,6 @@ } $loaded[$hook] = TRUE; } - if ($sort) { if (!isset($sorted_implementations[$hook])) { $sorted_implementations[$hook] = $implementations[$hook]; === modified file 'includes/theme.inc' --- includes/theme.inc 2009-01-11 22:00:08 +0000 +++ includes/theme.inc 2009-01-15 16:50:16 +0000 @@ -2010,7 +2010,11 @@ } // Clean up name so there are no underscores. $variables['template_files'][] = 'node-' . str_replace('_', '-', $node->type); - $variables['template_files'][] = 'node-' . $node->nid; + $variables['template_files'][] = 'node-' . $node->nid; + + // Add $FIELD_NAME_rendered variables for fields. + drupal_function_exists('field_attach_preprocess'); + $variables += field_attach_preprocess('node', $node); } /** === added directory 'modules/field' === added file 'modules/field/D6 - D7 API changes.txt' --- modules/field/D6 - D7 API changes.txt 1970-01-01 00:00:00 +0000 +++ modules/field/D6 - D7 API changes.txt 2009-01-15 16:50:15 +0000 @@ -0,0 +1,59 @@ +- content_types() -> field_info_*(); +- content_fields() -> field_info_*() +- _content_field_types() -> field_info_field_types() + _content_widget_types() -> field_info_widget_types() +- conntent_view_field() -> field_view_field(), with new signature +- hook_field_info(), hook_widget_info(), hook_field_formatter_info(): + hook_field_info() needs to specify default widget and formatters + new format for 'behaviors' + names of widget types and formatters need to be prefixed with the module name to avoid name clashes. +- pattern for formatter theme functions has changed +- hook_default_value() -> hook_widget_default_value() + +hook comparison: D7 D6 Remarks +----------------------------------------------------------------------------------------------------------------------------- +hook_field_info() unchanged +hook_field_columns($field) hook_field_settings('database columns') defines 'data pins' in SQL-lingo +hook_field_settings($field_type) hook_field_settings('save') also returns default values for the settings - move into hook_field_info() ? +hook_field_settings_form($field) hook_field_settings('form') only for UI +hook_field_settings_form_validate($field) hook_field_settings('validate') only for UI + fully deprecate in favor of regular FAPI validation ? +hook_field_is_empty($item, $field) unchanged Unlike other hook_field_*(), this operatates on a single value ($item, not $items) +Previous hook_field() $op : +hook_field_validate($obj_type, $object, $field, $instance, $items, $form) +hook_field_presave($obj_type, $object, $field, $instance, $items) Use cases ? +hook_field_load($obj_type, $object, $field, $instance, $items) +hook_field_insert($obj_type, $object, $field, $instance, $items) +hook_field_update($obj_type, $object, $field, $instance, $items) +hook_field_delete($obj_type, $object, $field, $instance, $items) +hook_field_delete_revision($obj_type, $object, $field, $instance, $items) +hook_field_prepare_translation($obj_type, $object, $field, $instance, $items) +hook_field_sanitize($obj_type, $object, $field, $instance, $items) Typically operates on single values, like hook_field_is_empty(). + +hook_field_instance_settings($field_type) - also returns default values for the settings - move into hook_field_info() ? +hook_field_instance_settings_form($instance) - only for UI +hook_field_instance_settings_form_validate($instance) - only for UI + fully deprecate for regular FAPI validation ? + +hook_widget_info() unchanged +hook_widget_settings($widget_type) hook_widget_settings('save') also returns default values for the settings - move into hook_widget_info() ? +hook_widget_settings_form($instance) hook_widget_settings('form') only for UI +hook_widget_settings_form_validate($instance) hook_widget_settings('validate') only for UI + fully deprecate in favor of regular FAPI validation ? +hook_widget() unchanged return FAPI element +hook_widget_default_value() hook_default_value() Signature looks strange + +hook_field_formatter_info() unchanged +hook_field_formatter_settings($formatter_type) - also returns default values for the settings - move into hook_field_formatter_info() ? + +hook_field(): D6 $op hook_field_*() Default implementation +-------------------------------------------------------------------------- +'validate' x x (currently unused) +'presave' x (use cases ?) x +'load' x - (content_storage) +'insert' x - (content_storage) +'update' x - (content_storage) +'delete' x - (content_storage) +'delete revision' x - (content_storage) +'prepare translation' x (noderef uses it) x +'view' - x +'preprocess_node' - x + +'sanitize' x - === added file 'modules/field/field.attach.inc' --- modules/field/field.attach.inc 1970-01-01 00:00:00 +0000 +++ modules/field/field.attach.inc 2009-01-15 16:50:15 +0000 @@ -0,0 +1,365 @@ +data as $key => $value) { + $obj->$key = $value; + } + } + else { + $queried_objs[$id] = $objects[$id]; + } + } + // Fetch other nodes from the database. + if ($queried_objs) { + // We need the raw additions to be able to cache them, so + // content_storage_load() and hook_field_load() must not alter + // nodes directly but return their additions. + $additions = module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'load', $obj_type, $queried_objs, $age); + foreach ($additions as $id => $obj_additions) { + foreach ($obj_additions as $key => $value) { + $queried_objs[$id]->$key = $value; + } + } + + // TODO D7 : to be consistent we might want to make hook_field_load() accept + // multiple objects too. Which forbids going through _field_invoke(), but + // requires manually iterating the instances instead. + foreach ($queried_objs as $id => $object) { + $custom_additions = _field_invoke('load', $obj_type, $object); + foreach ($custom_additions as $key => $value) { + $queried_objs[$id]->$key = $value; + $additions[$id][$key] = $value; + } + + // Let other modules act on loading the object. + foreach (module_implements('field_attach_load') as $module) { + $function = $module .'_field_attach_load'; + $function($obj_type, $queried_objs[$id]); + } + + // Cache the data. + list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $obj); + if ($cacheable) { + $cid = "field:$obj_type:$id:$vid"; + $data = isset($additions[$id]) ? $additions[$id] : array(); + cache_set($cid, $data, 'cache_field'); + } + } + } +} + +/** + * Load all fields for a previous version of each of a set of + * objects of a single object type. + * + * @param $obj_type + * The type of objects for which to load fields; e.g. 'node' or + * 'user'. + * @param $objects + * An array of objects for which to load fields. The keys for + * primary id, revision id, and bundle name to load are identified by + * hook_fieldable_info for $obj_type. + * @returns + * On return, the objects in $objects are modified by having the + * appropriate set of fields added. + */ +function _field_attach_load_revision($obj_type, $objects) { + return field_attach_load($obj_type, $objects, FIELD_LOAD_REVISION); +} + +/** + * + */ +function _field_attach_validate($obj_type, &$object, $form = NULL) { + _field_invoke('validate', $obj_type, $object, $form); + _field_invoke_default('validate', $obj_type, $object, $form); + + // Let other modules validate the object. + foreach (module_implements('field_attach_validate') as $module) { + $function = $module .'_field_attach_validate'; + $function($obj_type, $object, $form); + } +} + +/** + * Accounts for d-n-d reordering of field values. + * TODO D7 : rename to 'field_attach_submit' ? + */ +function _field_attach_presave($obj_type, &$object) { + _field_invoke('presave', $obj_type, $object); + _field_invoke_default('presave', $obj_type, $object); + + // Let other modules act on presaving the object. + foreach (module_implements('field_attach_presave') as $module) { + $function = $module .'_field_attach_presave'; + $function($obj_type, $object); + } +} + +/** + * + */ +function _field_attach_insert($obj_type, &$object) { + _field_invoke('insert', $obj_type, $object); + module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'write', $obj_type, $object); + + // Let other modules act on inserting the object. + foreach (module_implements('field_attach_insert') as $module) { + $function = $module .'_field_attach_insert'; + $function($obj_type, $object); + } + + list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object); + if ($cacheable) { + cache_clear_all("field:$obj_type:$id:", 'cache_field', TRUE); + } +} + +/** + * + */ +function _field_attach_update($obj_type, &$object) { + _field_invoke('update', $obj_type, $object); + module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'write', $obj_type, $object, TRUE); + + // Let other modules act on updating the object. + foreach (module_implements('field_attach_update') as $module) { + $function = $module .'_field_attach_update'; + $function($output, $obj_type, $object); + } + + list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object); + if ($cacheable) { + cache_clear_all("field:$obj_type:$id:$vid", 'cache_field'); + } +} + +/** + * + */ +function _field_attach_delete($obj_type, &$object) { + _field_invoke('delete', $obj_type, $object); + module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'delete', $obj_type, $object); + + // Let other modules act on deleting the object. + foreach (module_implements('field_attach_delete') as $module) { + $function = $module .'_field_attach_delete'; + $function($obj_type, $object); + } + + list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object); + if ($cacheable) { + cache_clear_all("field:$obj_type:$id:", 'cache_field', TRUE); + } +} + +/** + * + */ +function _field_attach_delete_revision($obj_type, &$object) { + _field_invoke('delete revision', $obj_type, $object); + module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'delete_revision', $obj_type, $object); + + // Let other modules act on deleting the revision. + foreach (module_implements('field_attach_delete_revision') as $module) { + $function = $module .'_field_attach_delete_revision'; + $function($obj_type, $object); + } + + list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object); + if ($cacheable) { + cache_clear_all("field:$obj_type:$id:$vid", 'cache_field'); + } +} + +/** + * + */ +function _field_attach_view($obj_type, &$object, $teaser = FALSE, $page = FALSE) { + // Let field modules sanitize their data for output. + _field_invoke('sanitize', $obj_type, $object); + + $output = _field_invoke_default('view', $obj_type, $object, $teaser, $page); + + // Let other modules make changes after rendering the view. + foreach (module_implements('field_attach_view') as $module) { + $function = $module .'_field_attach_view'; + $function($output, $obj_type, $object, $teaser, $page); + } + + return $output; + +} + +/** + * To be called in entity preprocessor. + * + * - Adds $FIELD_NAME_rendered variables + * containing the themed output for the whole field. + * - Adds the formatted values in the 'view' key of the items. + */ +function _field_attach_preprocess($obj_type, &$object) { + return _field_invoke_default('preprocess', $obj_type, $object); +} + +/** + * Implementation of hook_nodeapi_prepare_translation. + */ +function _field_attach_prepare_translation(&$node) { + // Prevent against invalid 'nodes' built by broken 3rd party code. + if (isset($node->type)) { + $type = content_types($node->type); + // Save cycles if the type has no fields. + if (!empty($type['instances'])) { + $default_additions = _field_invoke_default('prepare translation', $node); + $additions = _field_invoke('prepare translation', $node); + // Merge module additions after the default ones to enable overriding + // of field values. + $node = (object) array_merge((array) $node, $default_additions, $additions); + } + } +} + +/** + * Notify field.module that a new bundle was created. + * + * The default SQL-based storage doesn't need to do anytrhing about it, but + * others might. + * + * @param $bundle + * The name of the newly created bundle. + */ +function _field_attach_create_bundle($bundle) { + module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'create_bundle', $bundle); + + // Clear the cache. + field_cache_clear(); + + foreach (module_implements('field_attach_create_bundle') as $module) { + $function = $module .'_field_attach_create_bundle'; + $function($bundle); + } +} + +/** + * Notify field.module that a bundle was renamed. + * + * @param $bundle_old + * The previous name of the bundle. + * @param $bundle_new + * The new name of the bundle. + */ +function _field_attach_rename_bundle($bundle_old, $bundle_new) { + module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'rename_bundle', $bundle_old, $bundle_new); + db_update('field_config_instance') + ->fields(array('bundle' => $bundle_new)) + ->condition('bundle', $bundle_old) + ->execute(); + + // Clear the cache. + field_cache_clear(); + + foreach (module_implements('field_attach_rename_bundle') as $module) { + $function = $module .'_field_attach_rename_bundle'; + $function($bundle_old, $bundle_new); + } +} + +/** + * Notify field.module the a bundle was deleted. + * + * This deletes the data for the field instances as well as the field instances + * themselves. This function actually just marks the data and field instances + * and deleted, leaving the garbage collection for a separate process, because + * it is not always possible to delete this much data in a single page request + * (particularly since for some field types, the deletion is more than just a + * simple DELETE query). + * + * @param $bundle + * The bundle to delete. + */ +function _field_attach_delete_bundle($bundle) { + // Let other modules act on deleting the bundle + foreach (module_implements('field_attach_delete_bundle') as $module) { + $function = $module .'_field_attach_delete_bundle'; + $function($bundle); + } + + // Delete the instances themseves + $instances = field_info_instances($bundle); + foreach ($instances as $instance) { + field_delete_instance($instance['field_name'], $bundle); + } +} + +/** + * Helper function to extract id, vid, and bundle name from an object. + * + * Made public so that it can be used by modules implementing + * hook_field_attach_op(). + */ +function _field_attach_extract_ids($object_type, $object) { + // TODO D7 : prevent against broken 3rd party $node without 'type'. + $info = field_info_fieldable_types($object_type); + // Objects being created might not have id/vid yet. + $id = isset($object->{$info['id key']}) ? $object->{$info['id key']} : NULL; + $vid = ($info['revision key'] && isset($object->{$info['revision key']})) ? $object->{$info['revision key']} : NULL; + // If no bundle key provided, then we assume a single bundle, named after the + // type of the object. + $bundle = $info['bundle key'] ? $object->{$info['bundle key']} : $object_type; + $cacheable = isset($info['cacheable']) ? $info['cacheable'] : FALSE; + return array($id, $vid, $bundle, $cacheable); +} === added file 'modules/field/field.autoload.inc' --- modules/field/field.autoload.inc 1970-01-01 00:00:00 +0000 +++ modules/field/field.autoload.inc 2009-01-15 16:50:15 +0000 @@ -0,0 +1,225 @@ + $field['type']))); + } + + // Ensure the field name is unique. We also check disabled or deleted fields. + // TODO : do we want specific messages when clashing with a disabled or inactive field ? + $prior_field = field_read_field($field['field_name'], array('include_inactive' => TRUE, 'include_deleted' => TRUE)); + if (!empty($prior_field)) { + throw new FieldException(t('Attempt to create field name %name which already exists.', array('%name' => $field['field_name']))); + } + + $field += array( + 'cardinality' => 1, + 'locked' => FALSE, + 'settings' => array(), + ); + $module = $field_type['module']; + // Create all per-field-type properties (needed here as long as we have + // settings that impact column definitions). + $field['settings'] += field_info_field_settings($field['type']); + $field['module'] = $module; + $field['active'] = 1; + $field['deleted'] = 0; + // Create the data table. We need to populate the field columns, even though + // we don't actually store them. + $field['columns'] = _field_columns($field); + module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'create_table', $field); + + drupal_write_record('field_config', $field); + + // Clear caches + field_cache_clear(TRUE); +} + +function field_read_field($field_name, $include_additional = array()) { + $fields = field_read_fields(array('field_name' => $field_name), $include_additional); + return $fields ? current($fields) : FALSE; +} + +/** + * Read in fields that match an array of conditions. + * + * @params + * An array of conditions to match against. + * @include_additional + * The default behavior of this function is to not return fields that + * are inactive or have been deleted. Setting $include_additional['include_inactive'] + * or $include_additional['include_deleted'] will override this behavior. + */ +function field_read_fields($params = array(), $include_additional = array()) { + $query = db_select('field_config', 'fc', array('fetch' => PDO::FETCH_ASSOC)); + $query->fields('fc'); + + // Turn the conditions into a query. + foreach ($params as $key => $value) { + $query->condition($key, $value); + } + if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) { + $query->condition('fc.active', 1); + } + if (!isset($include_additional['include_deleted']) || !$include_additional['include_deleted']) { + $query->condition('fc.deleted', 0); + } + + $fields = array(); + $results = $query->execute(); + foreach ($results as $field) { + // drupal_write_record() writes an empty string for empty arrays. + $field['settings'] = $field['settings'] ? unserialize($field['settings']) : array(); + + module_invoke_all('field_read_field', $field); + + // Populate storage columns. + $field['columns'] = _field_columns($field); + + + $fields[$field['field_name']] = $field; + } + return $fields; +} + +/** + * Mark a field for deletion, including all its instances and all data + * associated with it. + */ +function field_delete_field($field_name) { + // Mark the field for deletion. + db_update('field_config') + ->fields(array('deleted' => 1)) + ->condition('field_name', $field_name) + ->execute(); + + // Mark any instances of the field for deletion. + db_update('field_config_instance') + ->fields(array('deleted' => 1)) + ->condition('field_name', $field_name) + ->execute(); + module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'delete_table', $field_name); + // Clear the cache. + field_cache_clear(TRUE); +} + +/** + * Populate the storage columns for a field. + */ +function _field_columns($field) { + $columns = (array) module_invoke($field['module'], 'field_columns', $field); + // Actual column name : prefix with field name. + foreach($columns as $column_name => $attributes) { + $columns[$column_name]['column'] = $field['field_name'] . '_' . $column_name; + } + return $columns; +} + +/** + * Creates an instance of a field, binding it to a bundle. + * @param $instance + * An associative array represeting an instance structure. Keys and values: + * field_name: The name of the field. + * bundle: The bundle this field belongs to. (For eg. node_story) + * label: The human-readable name for this instance. + * description: A description of what this instance holds. + * weight: The weight for this instance, used by the Form API + * representation, view rendering, etc. + * settings: Array of instance-specific properties (e.g. whether a + * user-reference shows as a backreference on userpages, or allowed + * file extensions on an update field). + * widget: An associative array storing the edit widget and its settings. + * type: The widget type (e.g. text_textfield or text_textarea). + * @TODO: cleanup from this point. + * Internal properties: + * $module: The module that defines the widget type. + * $active: Whether $module is enabled. + * $deleted: Always set to 0. Indicates that the field instance is not + * marked for deletion. + * $settings: Per-widget-type settings (e.g. rows for text_textarea). + * public $display: An array mapping display context names to + * public $display_settings: An array mapping display context names to + * properties. The properties can include: + * 'label': How the label should be displayed when this field is + * rendered: 'above', 'inline', 'hidden'. + * 'formatter': TODO. + * 'exclude': Whether to omit this field value from the rendered content. + * public $default_value. TODO: DEFINE. + * public $default_value_php. TODO: DEFINE. + * + ** Internal properties. These are protected if possible. + * public $widget_active; + * protected $field; + * protected $_field_name; + * protected $widget_class = 'WidgetSettings'; + */ +function field_create_instance($instance) { + // Check that the specified field exists. + $field = field_read_field($instance['field_name']); + if (empty($field)) { + throw new FieldException("Attempt to create an instance of a field that doesn't exist."); + } + + // TODO: Check that the specifed bundle exists. + + // TODO: Check that the widget type is known and can handle the field type ? + // TODO: Check that the formatters are known and can handle the field type ? + // TODO: Check that the display build modes are known for the object type ? + // Those checks should probably happen in _field_write_instance() ? + // Problem : this would mean that CCK cannot update an instance with a disabled formatter. + + // Ensure the field instance is unique. + // TODO : do we want specific messages when clashing with a disabled or inactive instance ? + $prior_instance = field_read_instance($instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE, 'include_deleted' => TRUE)); + if (!empty($prior_instance)) { + throw new FieldException('Attempt to create a field instance which already exists.'); + } + + _field_write_instance($instance); + + // Clear caches + field_cache_clear(); + return FALSE; +} + +/* + * Update an instance of a field. + * + * @param $instance + * An associative array represeting an instance structure. The required + * keys and values are: + * field_name: The name of an existing field. + * bundle: The bundle this field belongs to. + * + * @see field_create_instance() + */ +function field_update_instance($instance) { + // Check that the specified field exists. + $field = field_read_field($instance['field_name']); + if (empty($field)) { + throw new FieldException("Attempt to update an instance of a nonexistent field."); + } + + // Check that the field instance exists (even if it is inactive, since we + // want to be able to replace inactive widgets with new ones). + $prior_instance = field_read_instance($instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE)); + if (empty($prior_instance)) { + throw new FieldException("Attempt to update a field instance that doesn't exist."); + } + + _field_write_instance($instance, TRUE); + + // Clear caches. + field_cache_clear(); +} + +function _field_write_instance($instance, $update = FALSE) { + $field = field_read_field($instance['field_name']); + $field_type = field_info_field_types($field['type']); + + // Set defaults. + $instance += array( + 'settings' => array(), + 'display' => array(), + 'widget' => array(), + 'required' => FALSE, + 'label' => $instance['field_name'], + 'description' => '', + 'weight' => 0, + 'deleted' => 0, + ); + + // Set default instance settings. + $instance['settings'] += field_info_instance_settings($field['type']); + + // Set default widget and settings. + $instance['widget'] += array( + // TODO: what if no 'default_widget' specified ? + 'type' => $field_type['default_widget'], + 'settings' => array(), + ); + // Check widget module. + $widget_type = field_info_widget_types($instance['widget']['type']); + $widget_module = $widget_type['module']; + $widget_active = module_exists($widget_module); + $instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']); + $instance['widget']['module'] = $widget_module; + $instance['widget']['active'] = $widget_active; + + // Make sure there is at least display info for the 'full' context. + $instance['display'] += array( + 'full' => array(), + ); + // Set default display settings for each context. + foreach ($instance['display'] as $context => $display) { + $instance['display'][$context] += array( + 'label' => 'above', + 'exclude' => 0, + // TODO: what if no 'default_formatter' specified ? + 'type' => $field_type['default_formatter'], + 'settings' => array(), + ); + $formatter_type = field_info_formatter_types($instance['display'][$context]['type']); + // TODO : 'hidden' will raise PHP warnings. + $instance['display'][$context]['module'] = $formatter_type['module']; + $instance['display'][$context]['settings'] += field_info_formatter_settings($instance['display'][$context]['type']); + } + + // Create $data to contain everything from $instance that does not + // have its own column, and thus will be stored serialized. + $data = $instance; + unset($data['field_name'], $data['bundle'], $data['widget']['type'], $data['weight'], $data['deleted']); + + $record = array( + 'field_name' => $instance['field_name'], + 'bundle' => $instance['bundle'], + 'widget_type' => $instance['widget']['type'], + 'widget_module' => $widget_module, + 'widget_active' => $widget_active, + 'weight' => $instance['weight'], + 'data' => $data, + 'deleted' => $instance['deleted'], + ); + // We need to tell drupal_update_record() the primary keys to trigger an + // update. + $primary_keys = $update ? array('field_name', 'bundle') : array(); + drupal_write_record('field_config_instance', $record, $primary_keys); +} + +function field_read_instance($field_name, $bundle, $include_additional = array()) { + $instances = field_read_instances(array('field_name' => $field_name, 'bundle' => $bundle), $include_additional); + return $instances ? current($instances) : FALSE; +} + +/** + * Load a field instance. + * + * @param $param + * An array of properties to use in selecting a field instance. Valid keys: + * - 'bundle' - The name of the bundle (i.e. 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. + * @param $include_additional + * The default behavior of this function is to not return field instances that + * are inactive or have been marked deleted. Setting $include_additional['include_inactive'] + * or $include_additional['include_deleted'] will override this behavior. + * @return + * The field arrays. + */ +function field_read_instances($params = array(), $include_additional = array()) { + $query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC)); + $query->join('field_config', 'fc', 'fc.field_name = fci.field_name'); + $query->fields('fci'); + #$query->fields('fc', array('type')); + + // Turn the conditions into a query. + foreach ($params as $key => $value) { + $query->condition('fci.'.$key, $value); + } + $query->condition('fc.active', 1); + if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) { + $query->condition('fci.widget_active', 1); + } + if (!isset($include_additional['include_deleted']) || !$include_additional['include_deleted']) { + $query->condition('fc.deleted', 0); + $query->condition('fci.deleted', 0); + } + + $instances = array(); + $results = $query->execute(); + + foreach ($results as $record) { + $instance = unserialize($record['data']); + $instance['field_name'] = $record['field_name']; + $instance['bundle'] = $record['bundle']; + $instance['weight'] = $record['weight']; + $instance['deleted'] = $record['deleted']; + $instance['widget']['type'] = $record['widget_type']; + $instance['widget']['module'] = $record['widget_module']; + $instance['widget']['active'] = $record['widget_active']; + + // TODO D7 : Set default widget settings, default instance settings, default display settings. + // (the modules that defined them might have changed since the instance was last saved). + + module_invoke_all('field_read_instance', $instance); + $instances[] = $instance; + } + return $instances; +} + +/** + * Mark a field instance for deletion, including all data associated with + * it. + * + * @param $field_name + * The name of the field whose instance will be deleted. + * @param $bundle + * The bundle for the instance which will be deleted. + */ +function field_delete_instance($field_name, $bundle) { + // Mark the field instance for deletion. + db_update('field_config_instance') + ->fields(array('deleted' => 1)) + ->condition('field_name', $field_name) + ->condition('bundle', $bundle) + ->execute(); + + // Mark all data associated with the field for deletion. + module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'delete_instance', $field_name, $bundle); + // Clear the cache. + field_cache_clear(); +} + +/** + * @} End of "defgroup field_crud". + */ \ No newline at end of file === added file 'modules/field/field.default.inc' --- modules/field/field.default.inc 1970-01-01 00:00:00 +0000 +++ modules/field/field.default.inc 2009-01-15 16:50:16 +0000 @@ -0,0 +1,271 @@ +content['field_foo']); + * + * The code supports both single value formatters, which theme an individual + * item value, 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 $object array will look like: + * $object->content['field_foo']['wrapper'] = array( + * '#type' => 'field', + * '#title' => 'label' + * '#field_name' => 'field_name', + * '#object' => $object, + * '#object_type' => $obj_type, + * // Value of the $teaser param of hook_nodeapi('view'). + * '#teaser' => $teaser, + * 'items' => + * 0 => array( + * '#item' => $items[0], + * // Only for 'single-value' formatters + * '#theme' => $theme, + * '#field_name' => 'field_name', + * '#bundle' => $bundle, + * '#formatter' => $formatter_name, + * '#settings' => $formatter_settings, + * '#object' => $object, + * '#object_type' => $obj_type, + * '#delta' => 0, + * ), + * 1 => array( + * '#item' => $items[1], + * // Only for 'single-value' formatters + * '#theme' => $theme, + * '#field_name' => 'field_name', + * '#bundle' => $bundle_name, + * '#formatter' => $formatter_name, + * '#settings' => $formatter_settings, + * '#object' => $object, + * '#object_type' => $obj_type, + * '#delta' => 1, + * ), + * // Only for 'multiple-value' formatters + * '#theme' => $theme, + * '#field_name' => 'field_name', + * '#bundle' => $bundle_name, + * '#formatter' => $formatter_name, + * '#settings' => $formatter_settings, + * ), + * ); + */ +function field_field_view($obj_type, $object, $field, $instance, $items, $teaser) { + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + + $addition = array(); + + // Entities without build modes should provide a 'full' context. + // NODE_BUILD_NORMAL is 0, and ('whatever' == 0) is TRUE, so we need a ===. + if (!isset($object->build_mode)) { + $context = 'full'; + } + elseif ($object->build_mode === NODE_BUILD_NORMAL + || $object->build_mode == NODE_BUILD_PREVIEW) { + $context = $teaser ? 'teaser' : 'full'; + } + else { + $context = $object->build_mode; + } + + // If we don't have specific settings for the current build_nmode, we use the + // (required) 'full' build_mode. + $display = isset($instance['display'][$context]) ? $instance['display'][$context] : $instance['display']['full']; + // Ensure we have a valid formatter and formatter settings. + $display = _field_get_formatter($display, $field); + + if ($display['type'] && $display['type'] !== 'hidden') { + $theme = 'field_formatter_'. $display['type']; + $single = (field_behaviors_formatter('multiple values', $display) == FIELD_BEHAVIOR_DEFAULT); + + $label_display = $display['label']; + if (isset($object->build_mode) && $object->build_mode == NODE_BUILD_SEARCH_INDEX) { + $label_display = 'hidden'; + } + + $info = array( + '#field_name' => $field['field_name'], + '#bundle' => $bundle, + '#object' => $object, + '#object_type' => $obj_type, + ); + + $element = $info + array( + '#type' => 'field', + '#title' => check_plain(t($instance['label'])), + '#access' => field_access('view', $field), + '#label_display' => $label_display, + '#teaser' => $teaser, + '#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 = $info + array( + '#formatter' => $display['type'], + '#settings' => $display['settings'], + '#theme' => $theme, + ); + + if ($single) { + foreach ($items as $delta => $item) { + $element['items'][$delta] += $format_info; + $element['items'][$delta]['#item']['#delta'] = $delta; + } + } + else { + $element['items'] += $format_info; + } + + // The wrapper lets us get the themed output for the whole field + // to populate the $FIELD_NAME_rendered variable for templates, + // and hide it from the $content variable if needed. + // See 'preprocess' op and theme_content_field_wrapper()? + $wrapper = $info + array( + 'field' => $element, + '#weight' => $instance['weight'], + '#post_render' => array('field_wrapper_post_render'), + '#context' => $context, + ); + + $addition = array($field['field_name'] => $wrapper); + } + return $addition; +} + +/** + * Hide excluded fields from the $content variable in templates. + */ +function field_wrapper_post_render($content, $element) { + $instance = field_info_instance($element['#field_name'], $element['#bundle']); + if (theme('field_exclude', $content, $instance, $element['#context'])) { + return ''; + } + return $content; +} + +/** + * 'Theme' function for a field's addition to the combined template output, + * i.e. the node's $content or the user's $user_profile value. + * This allows more flexibility in templates : you can use custom markup + * around a few specific fields, and print the rest normally. + * + * This is a theme function, so it can be overridden in different + * themes to produce different results. + * + * The html for individual fields and groups are available in the + * $FIELD_NAME_rendered and $GROUP_NAME_rendered variables. + * + * @return + * Whether or not the field's content is to be added in this context. + * Uses the 'exclude' value from the field's display settings. + */ +function theme_field_exclude($content, $object, $context) { + if (empty($object['display']) + || empty($object['display'][$context]) + || empty($object['display'][$context]['exclude'])) { + return FALSE; + } + else { + return TRUE; + } +} + +function field_field_preprocess($obj_type, $object, $field, $instance, &$items) { + return array( + $field['field_name'] .'_rendered' => isset($object->content[$field['field_name']]['#children']) ? $object->content[$field['field_name']]['#children'] : '', + ); +} + +function field_field_prepare_translation($obj_type, $object, $field, $instance, &$items) { + $addition = array(); + if (isset($object->translation_source->$field['field_name'])) { + $addition[$field['field_name']] = $object->translation_source->$field['field_name']; + } + return $addition; +} + +/** + * Helper function to return the correct default value for a field. + * + * @param $object + * The object. + * @param $field + * The field array. + * @param $items + * The value of the field in the node. + * @return + * The default value for that field. + */ +// TODO D7: [yched] I really don't get the signature of this function - &$form, &$form_state ?? +// Karen, does it ring a bell for you ? +// [Karen] It is called from field_field_form() and the parameter order matches +// the D6 parameters that content_field_form() received -- it passes the +// same parameters it receives on to the field modules in the same order. +// I'm altering it to match the new parameters in field_field_form(). +// [yched] The new order of arguments definitely makes sense. +// What I don't get is why we pass $form and $form_state: the only goal of the function is to return +// a default '$items' sructure, not a partial form array. $items structure are form and widget agnostic. +// The fact that we then use this default $items in a form (field_field_form()) is just incidental; +// conceptually we should be able to ask "what is the default $items value of this field" from a non form context. +// I wonder what info would be needed from $form or $form_state that's not already available in $obj_type, $object, +// $field, $instance ? +function field_widget_default_value($obj_type, $object, $field, $instance, &$form, &$form_state, $delta) { + $default_value = array(); + if (!empty($instance['widget']['default_value_php'])) { + ob_start(); + $result = eval($instance['widget']['default_value_php']); + ob_end_clean(); + if (is_array($result)) { + $default_value = $result; + } + } + elseif (!empty($instance['widget']['default_value'])) { + $default_value = $instance['widget']['default_value']; + } + return (array) $default_value; +} \ No newline at end of file === added file 'modules/field/field.info' --- modules/field/field.info 1970-01-01 00:00:00 +0000 +++ modules/field/field.info 2009-01-15 16:50:15 +0000 @@ -0,0 +1,13 @@ +; $Id$ +name = Field +description = Field API to add fields to objects like nodes and users. +package = Core - fields +core = 7.x +files[] = field.module +files[] = field.install +files[] = field.crud.inc +files[] = field.default.inc +files[] = field.attach.inc +files[] = field.node_form.inc +files[] = field.autoload.inc +dependencies[] = field_sql_storage === added file 'modules/field/field.install' --- modules/field/field.install 1970-01-01 00:00:00 +0000 +++ modules/field/field.install 2009-01-15 16:50:15 +0000 @@ -0,0 +1,153 @@ + array( + 'field_name' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => 'The name of this field', + ), + 'type' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'description' => 'The type of this field, coming from a field module', + ), + 'locked' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + 'description' => '@TODO', + ), + 'settings' => array( + 'type' => 'text', + 'size' => 'medium', + 'not null' => TRUE, + 'serialize' => TRUE, + 'description' => 'Field specific settings, for example maximum length', + ), + 'module' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'cardinality' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'active' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'deleted' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('field_name'), + 'indexes' => array( + // used by field_read_fields + 'active_deleted' => array('active', 'deleted'), + // used by field_modules_disabled + 'module' => array('module'), + // used by field_associate_fields + 'type' => array('type'), + ), + ); + $schema['field_config_instance'] = array( + 'fields' => array( + 'field_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'bundle' => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''), + 'widget_type' => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''), + 'widget_module' => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''), + 'widget_active' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'data' => array( + 'type' => 'text', + 'size' => 'medium', + 'not null' => TRUE, + 'serialize' => TRUE, + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'deleted' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('field_name', 'bundle'), + 'indexes' => array( + // used by field_read_instances + 'widget_active_deleted' => array('widget_active', 'deleted'), + // used by field_modules_disabled + 'widget_module' => array('widget_module'), + // used by field_associate_fields + 'widget_type' => array('widget_type'), + ), + ); + $schema['cache_field'] = drupal_get_schema_unprocessed('system', 'cache'); + + return $schema; +} === added file 'modules/field/field.module' --- modules/field/field.module 1970-01-01 00:00:00 +0000 +++ modules/field/field.module 2009-01-15 16:50:16 +0000 @@ -0,0 +1,1138 @@ + 'field_add_more_js', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +/** + * Implementation of hook elements(). + */ +function field_elements() { + return array( + 'field' => array(), + ); +} + +/** + * Implementation of hook_theme(). + */ +function field_theme() { + $path = drupal_get_path('module', 'field') .'/theme'; + + return array( + 'field' => array( + 'template' => 'field', + 'arguments' => array('element' => NULL), + 'path' => $path, + ), + // TODO D7 : do we need that in core ? + // Probably no easy way for a contrib module to do this + // since the Field module creates and processes the template, + // so maybe it must be in core. In core for now. + // [yched]: This is just adding + // '#post_render' => array('field_wrapper_post_render') + // at the right places in the render array generated by field_field_view(). + // Can be done in hook_field_attach_post_view if we want. + 'field_exclude' => array( + 'arguments' => array('content' => NULL, 'object' => array(), 'context' => NULL), + ), + 'field_multiple_value_form' => array( + 'arguments' => array('element' => NULL), + ), + ); +} + +/** + * Implementation of hook_modules_installed(). + */ +function field_modules_installed($modules) { + field_cache_clear(); +} + +/** + * Implementation of hook_modules_uninstalled(). + */ +function field_modules_uninstalled($modules) { + module_load_include('inc', 'field', 'field.crud'); + foreach ($modules as $module) { + field_module_delete($module); + } +} + +/** + * Implementation of hook_modules_enabled(). + */ +function field_modules_enabled($modules) { + foreach ($modules as $module) { + field_associate_fields($module); + } + field_cache_clear(); +} + +/** + * Implementation of hook_modules_disabled(). + */ +function field_modules_disabled($modules) { + foreach ($modules as $module) { + db_update('field_config') + ->fields(array('active' => 0)) + ->condition('module', $module) + ->execute(); + db_update('field_config_instance') + ->fields(array('widget_active' => 0)) + ->condition('widget_module', $module) + ->execute(); + field_cache_clear(TRUE); + } +} + +/** + * 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 field_associate_fields($module) { + $module_fields = module_invoke($module, 'field_info'); + if ($module_fields) { + foreach ($module_fields as $name => $field_info) { + watchdog('field', 'Updating field type %type with module %module.', array('%type' => $name, '%module' => $module)); + db_update('field_config') + ->fields(array('module' => $module, 'active' => 1)) + ->condition('type', $name) + ->execute(); + } + } + $module_widgets = module_invoke($module, 'widget_info'); + if ($module_widgets) { + foreach ($module_widgets as $name => $widget_info) { + watchdog('field', 'Updating widget type %type with module %module.', array('%type' => $name, '%module' => $module)); + db_update('field_config_instance') + ->fields(array('widget_module' => $module, 'widget_active' => 1)) + ->condition('widget_type', $name) + ->execute(); + } + } +} + +/** + * Helper function to filter out empty values. + * + * On order to keep marker rows in the database, the function ensures + * that the right number of 'all columns NULL' values is kept. + * + * @param array $field + * @param array $items + * @return array + * returns filtered and adjusted item array + */ +function field_set_empty($field, $items) { + // Filter out empty values. + $filtered = array(); + $function = $field['module'] .'_field_is_empty'; + foreach ((array) $items as $delta => $item) { + if (!$function($item, $field)) { + $filtered[] = $item; + } + } + return $filtered; +} + +/** + * Helper function to sort items in a field according to + * user drag-n-drop reordering. + */ +function _field_sort_items($field, $items) { + if (($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) && isset($items[0]['_weight'])) { + usort($items, '_field_sort_items_helper'); + foreach ($items as $delta => $item) { + if (is_array($items[$delta])) { + unset($items[$delta]['_weight']); + } + } + } + return $items; +} + +/** + * Sort function for items order. + * (copied form element_sort(), which acts on #weight keys) + */ +function _field_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; +} + +/** + * Same as above, using ['_weight']['#value'] + */ +function _field_sort_items_value_helper($a, $b) { + $a_weight = (is_array($a) && isset($a['_weight']['#value'])) ? $a['_weight']['#value'] : 0; + $b_weight = (is_array($b) && isset($b['_weight']['#value'])) ? $b['_weight']['#value'] : 0; + if ($a_weight == $b_weight) { + return 0; + } + return ($a_weight < $b_weight) ? -1 : 1; +} + +/** + * Invoke a field hook. + * + * @param $op + * - Possible operations include: + * - load + * - form + * - validate + * - presave + * - insert + * - update + * - delete + * - delete revision + * - sanitize + * - view + * - preprocess + * - prepare translation + * + * @param $obj_type + * - Can be: + * - node + * - user + * - Others not yet implemented. + * + * @param $object + * - The fully formed $obj_type object. + * + * @param $a + * - The $form in the 'form' operation. + * - The value of $teaser in the 'view' operation. + * - Otherwise NULL. + * + * @param $b + * - The value of $page in the 'view' operation. + * - Otherwise NULL. + * + * @param $default + * - TRUE: render the default field implementation of the field hook. + * - FALSE: render the field module's implementation of the field hook. + */ +function _field_invoke($op, $obj_type, &$object, &$a = NULL, &$b = NULL, $default = FALSE) { + list(, , $bundle) = field_attach_extract_ids($obj_type, $object); + $instances = field_info_instances($bundle); + + $return = array(); + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $field = field_info_field($field_name); + $items = isset($object->$field_name) ? $object->$field_name : array(); + + // Make sure AHAH 'add more' button isn't sent to the fields for processing. + // TODO D7 : needed ? + unset($items[$field_name .'_add_more']); + + $function = $default ? 'field_field_'. $op : $field['module'] .'_field_'. $op; + if (drupal_function_exists($function)) { + $result = $function($obj_type, $object, $field, $instance, $items, $a, $b); + 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($object->$field_name) || count($items)) { + $object->$field_name = $items; + } + } + + return $return; +} + +/** + * Invoke field.module's version of a field hook. + */ +function _field_invoke_default($op, $obj_type, &$object, &$a = NULL, &$b = NULL) { + return _field_invoke($op, $obj_type, $object, $a, $b, TRUE); +} + + +/** + * Helper function for determining the behavior of a field + * with respect to a given operation. + * + * @param $op + * The name of the operation. + * Currently supported : none + * // TODO D7: no use cases (yet ?) - do we want to keep that function ?. + * @param $field + * The field array. + * @return + * FIELD_BEHAVIOR_NONE - do nothing for this operation. + * FIELD_BEHAVIOR_CUSTOM - use the field's callback function. + * FIELD_BEHAVIOR_DEFAULT - use field.module default behavior. + */ +function field_behaviors_field($op, $field) { + $info = field_info_field_types($field['type']); + return isset($info['behaviors'][$op]) ? $info['behaviors'][$op] : FIELD_BEHAVIOR_DEFAULT; +} + +/** + * Helper function for determining the behavior of a widget + * with respect to a given operation. + * + * @param $op + * The name of the operation. + * Currently supported: 'default value', 'multiple values'. + * @param $instance + * The field instance array. + * @return + * FIELD_BEHAVIOR_NONE - do nothing for this operation. + * FIELD_BEHAVIOR_CUSTOM - use the widget's callback function. + * FIELD_BEHAVIOR_DEFAULT - use field.module default behavior. + */ +function field_behaviors_widget($op, $instance) { + $info = field_info_widget_types($instance['widget']['type']); + return isset($info['behaviors'][$op]) ? $info['behaviors'][$op] : FIELD_BEHAVIOR_DEFAULT; +} + +/** + * Helper function for determining the behavior of a formatter + * with respect to a given operation. + * + * @param $op + * The name of the operation. + * Currently supported: 'multiple values' + * @param $display + * The $instance['display'][$build_mode] array. + * @return + * FIELD_BEHAVIOR_NONE - do nothing for this operation. + * FIELD_BEHAVIOR_CUSTOM - use the formatter's callback function. + * FIELD_BEHAVIOR_DEFAULT - use field module default behavior. + */ +function field_behaviors_formatter($op, $display) { + $info = field_info_formatter_types($display['type']); + return isset($info['behaviors'][$op]) ? $info['behaviors'][$op] : FIELD_BEHAVIOR_DEFAULT; +} + +/** + * Return hook_field_info() data. + * + * @param $field_type + * (optional) A field type name. If ommitted, all field types will be + * returned. + * @return + * Either a field type description, as provided by hook_field_info(), or an + * array of all existing field types, keyed by field type name. + */ +function field_info_field_types($field_type = NULL) { + $info = _field_info_types(); + $field_types = $info['field types']; + if ($field_type) { + if (isset($field_types[$field_type])) { + return $field_types[$field_type]; + } + } + else { + return $field_types; + } +} + +/** + * Return hook_field_widget_info() data. + * + * @param $widget_type + * (optional) A widget type name. If ommitted, all widget types will be + * returned. + * @return + * Either a widget type description, as provided by + * hook_field_widget_info(), or an array of all existing widget + * types, keyed by widget type name. + */ +function field_info_widget_types($widget_type = NULL) { + $info = _field_info_types(); + $widget_types = $info['widget types']; + if ($widget_type) { + if (isset($widget_types[$widget_type])) { + return $widget_types[$widget_type]; + } + } + else { + return $widget_types; + } +} + +/** + * Return hook_field_formatter_info() data. + * + * @param $formatter_type + * (optional) A formatter type name. If ommitted, all formatter types will be + * returned. + * @return + * Either a formatter type description, as provided by hook_field_formatter_info(), + * or an array of all existing widget types, keyed by widget type name. + */ +function field_info_formatter_types($formatter_type = NULL) { + $info = _field_info_types(); + $formatter_types = $info['formatter types']; + if ($formatter_type) { + if (isset($formatter_types[$formatter_type])) { + return $formatter_types[$formatter_type]; + } + } + else { + return $formatter_types; + } +} + +/** + * Return hook_fieldable_info() data. + * + * @param $obj_type + * (optional) A fieldable type name. If ommitted, all fieldable types will be + * returned. + * @return + * Either a fieldable type description, as provided by hook_fieldable_info(), + * or an array of all existing fieldable types, keyed by fieldable type name. + */ +function field_info_fieldable_types($obj_type = NULL) { + $info = _field_info_types(); + $fieldable_types = $info['fieldable types']; + if ($obj_type) { + if (isset($fieldable_types[$obj_type])) { + return $fieldable_types[$obj_type]; + } + } + else { + return $fieldable_types; + } +} + +/** + * Return an array of fieldable bundle names and labels, for an individual + * object type or for all object types. + */ +function field_info_bundles($obj_type = NULL) { + $info = _field_info_types(); + $bundles = array(); + foreach ($info['fieldable types'] as $type => $fieldable_info) { + if (empty($obj_type) || $obj_type == $type) { + $bundles += $fieldable_info['bundles']; + } + } + return $bundles; +} + +/** + * Identity the type of entity that created a bundle. + * // TODO : might not be needed depending on how we solve + * // the 'namespace bundle names' issue + */ +function field_info_bundle_entity($bundle) { + $info = _field_info_types(); + foreach ($info['fieldable types'] as $type => $fieldable_info) { + if (isset($fieldable_info['bundles'][$bundle])) { + return $type; + } + } + return FALSE; +} + +/** + * Return array of all field data, keyed by field name. + * + * @return + * An array of Field objects. + */ +function field_info_fields() { + $info = _field_info_fields(); + return $info['fields']; +} + +/** + * Return data about an individual field. + * + * @param $field_name + */ +function field_info_field($field_name) { + $info = _field_info_fields(); + if (isset($info['fields'][$field_name])) { + return $info['fields'][$field_name]; + } +} + + +/** + * Return an array of instance data for a given bundle, + * or for all known bundles, keyed by bundle name and field name. + * + * @param $bundle_name + * If set, return information on just this bundle. + */ +function field_info_instances($bundle_name = NULL) { + $info = _field_info_fields(); + if (!isset($bundle_name)) { + return $info['instances']; + } + if (isset($info['instances'][$bundle_name])) { + return $info['instances'][$bundle_name]; + } + return array(); +} + +/** + * Return an array of instance data for a specific field and bundle. + */ +function field_info_instance($field_name, $bundle_name) { + $info = _field_info_fields(); + if (isset($info['instances'][$bundle_name][$field_name])) { + return $info['instances'][$bundle_name][$field_name]; + } +} + +/** + * Return valid formatter type and settings. + * + * Backs up to default formatter and settings if the intended formatter is no + * longer available. This might happen when the formatter has been renamed in + * the module, or if the module has been disabled since then. + */ +function _field_get_formatter($display, $field) { + if ($display['type'] != 'hidden') { + $formatter_type = field_info_formatter_types($display['type']); + if (empty($formatter_type)) { + $field_type = field_info_field_types($field['type']); + $display['type'] = $field_type['default_formatter']; + $formatter_type = field_info_formatter_types($display['type']); + } + $function = $formatter_type['module'] .'_field_formatter_settings'; + if (drupal_function_exists($function)) { + $display['settings'] += $function($display['type']); + } + } + return $display; +} + +/** + * Return a field type's default settings. + * + * @param $type + * A field type name. + * @return + * The field type's default settings, as provided by hook_field_info(), or an + * empty array. + */ +function field_info_field_settings($type) { + $info = field_info_field_types($type); + return isset($info['settings']) ? $info['settings'] : array(); +} + +/** + * Return a field type's default instance settings. + * + * @param $type + * A field type name. + * @return + * The field type's default instance settings, as provided by + * hook_field_info(), or an empty array. + */ +function field_info_instance_settings($type) { + $info = field_info_field_types($type); + return isset($info['instance_settings']) ? $info['instance_settings'] : array(); +} + +/** + * Return a field widget's default settings. + * + * @param $type + * A widget type name. + * @return + * The field type's default settings, as provided by hook_field_info(), or an + * empty array. + */ +function field_info_widget_settings($type) { + $info = field_info_widget_types($type); + return isset($info['settings']) ? $info['settings'] : array(); +} + +/** + * Return a field formatter's default settings. + * + * @param $type + * A field formatter type name. + * @return + * The field formatter's default settings, as provided by + * hook_field_info(), or an empty array. + */ +function field_info_formatter_settings($type) { + $info = field_info_formatter_types($type); + return isset($info['settings']) ? $info['settings'] : array(); +} + +/** + * Collate all information on field types, widget types and related structures. + * + * @param $reset + * If TRUE, clear the cache. The information will be rebuilt from the database + * next time it is needed. Defaults to FALSE. + * @return + * If $reset is TRUE, nothing. + * If $reset is FALSE, an array containing the following elements: + * + * field types: array of hook_field_info() results, keyed by field_type. + * * label, description, settings, instance_settings, default_widget, + * default_formatter, behaviors: from hook_field_info() + * * module: the module that exposes the field type + * + * widget types: array of hook_field_widget_info() results, keyed by + * widget_type. + * * label, field types, settings, behaviors: from hook_field_widget_info() + * * module: module that exposes the widget type + * + * formatter types: array of hook_field_formatter_info() results, keyed by + * formatter_type. + * * label, field types, behaviors: from hook_field_formatter_info() + * * module: module that exposes the formatter type + + * fieldable types: array of hook_fieldable_info() results, keyed by entity_type. + * * name, id key, revision key, bundle key, cacheable, bundles: from + * hook_fieldable_info() + * * module: module that exposes the entity type + */ +function _field_info_types($reset = FALSE) { + static $info; + + if ($reset) { + $info = NULL; + cache_clear_all('field_info_types', 'cache_field'); + return; + } + + if (!isset($info)) { + if ($cached = cache_get('field_info_types', 'cache_field')) { + $info = $cached->data; + } + else { + $info = array( + 'field types' => array(), + 'widget types' => array(), + 'formatter types' => array(), + 'fieldable types' => array(), + ); + + // TODO D7 : document max length for field types, widget types, formatter names... + + // Populate field types. + foreach (module_implements('field_info') as $module) { + $field_types = (array) module_invoke($module, 'field_info'); + foreach ($field_types as $name => $field_info) { + $info['field types'][$name] = $field_info; + $info['field types'][$name]['module'] = $module; + } + } + + // Populate widget types. + foreach (module_implements('field_widget_info') as $module) { + $widget_types = (array) module_invoke($module, 'field_widget_info'); + foreach ($widget_types as $name => $widget_info) { + $info['widget types'][$name] = $widget_info; + $info['widget types'][$name]['module'] = $module; + } + } + + // Populate formatters. + foreach (module_implements('field_formatter_info') as $module) { + $formatter_types = (array) module_invoke($module, 'field_formatter_info'); + foreach ($formatter_types as $name => $formatter_info) { + $info['formatter types'][$name] = $formatter_info; + $info['formatter types'][$name]['module'] = $module; + } + } + + // Populate information about 'fieldable' entities. + foreach (module_implements('fieldable_info') as $module) { + $fieldable_types = (array) module_invoke($module, 'fieldable_info'); + foreach ($fieldable_types as $name => $fieldable_info) { + // Provide defaults. + $fieldable_info += array( + 'revision key' => '', + 'bundle key' => '', + 'cacheable' => TRUE, + 'bundles' => array(), + ); + // If no bundle key provided, then we assume a single bundle, named + // after the type of the object. Make sure the bundle created + // has the human-readable name we need for bundle messages. + if (empty($fieldable_info['bundle key'])) { + $fieldable_info['bundles'] = array($name => $fieldable_info['name']); + } + $info['fieldable types'][$name] = $fieldable_info; + $info['fieldable types'][$name]['module'] = $module; + } + } + + cache_set('field_info_types', $info, 'cache_field'); + } + } + + return $info; +} + +/** + * Collate all information on existing fields and instances. + * + * @param $reset + * If TRUE, clear the cache. The information will be rebuilt from the database + * next time it is needed. Defaults to FALSE. + * @return + * If $reset is TRUE, nothing. + * If $reset is FALSE, an array containing the following elements: + * + * fields: array of all defined Field objects, keyed by field name + * + * instances: array of bundled field info, keyed by bundle name + * * contains all FieldInstance objects for this bundle, keyed by field name. + */ +function _field_info_fields($reset = FALSE) { + static $info; + + if ($reset) { + $info = NULL; + cache_clear_all('field_info_fields', 'cache_field'); + return; + } + + if (!isset($info)) { + if ($cached = cache_get('field_info_fields', 'cache_field')) { + $info = $cached->data; + } + else { + $info = array( + 'fields' => field_read_fields(), + 'instances' => array_fill_keys(array_keys(field_info_bundles()), array()), + ); + + // Populate instances. + $instances = field_read_instances(); + foreach ($instances as $instance) { + $info['instances'][$instance['bundle']][$instance['field_name']] = $instance; + } + + cache_set('field_info_fields', $info, 'cache_field'); + } + } + + return $info; +} + +/** + * Registry of available build modes. + * TODO : move into hook_fieldable_info() ? + */ +function field_build_modes($obj_type) { + static $info; + + if (!isset($info[$obj_type])) { + // module_invoke_all messes numeric keys. + // TODO : revisit when we move away from numeric build modes. + $info[$obj_type] = array(); + foreach (module_implements('field_build_modes') as $module) { + $info[$obj_type] += module_invoke($module, 'field_build_modes', $obj_type); + } + } + return $info[$obj_type]; +} + +/** + * Clear the cached information; called in several places when field + * information is changed. + */ +function field_cache_clear($rebuild_schema = FALSE) { + cache_clear_all('*', 'cache_field', TRUE); + _field_info_types(TRUE); + _field_info_fields(TRUE); + + // Refresh the schema to pick up new information. + // TODO : if db storage gets abstracted out, we'll need to revisit how and when + // we refresh the schema... + if ($rebuild_schema) { + $schema = drupal_get_schema(NULL, TRUE); + } +} + +/** + * Manipulate a 2D array to reverse rows and columns. + * + * // TODO D7 : do we need this ? Seems specific to option_widgets + * 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 field_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; +} + +/** + * Like filter_xss_admin(), but with a shorter list of allowed tags. + * + * Used for items entered by administrators, like field descriptions, + * allowed values, where some (mainly inline) mark-up may be desired + * (so check_plain() is not acceptable). + */ +function field_filter_xss($string) { + return filter_xss($string, _field_filter_xss_allowed_tags()); +} + +/** + * List of tags allowed by field_filter_xss(). + */ +function _field_filter_xss_allowed_tags() { + return array('a', 'b', 'big', 'code', 'del', 'em', 'i', 'ins', 'pre', 'q', 'small', 'span', 'strong', 'sub', 'sup', 'tt', 'ol', 'ul', 'li', 'p', 'br', 'img'); +} + +/** + * Human-readable list of allowed tags, for display in help texts. + */ +function _field_filter_xss_display_allowed_tags() { + return '<'. implode('> <', _field_filter_xss_allowed_tags()) .'>'; +} + +/** + * Format a field item for display. + * + * TODO D7 : do we still need field_format ? + * - backwards compatibility of templates - check what fallbacks we can propose... + * - used by Views integration in CCK D6 + * At least needs a little rehaul/update... + * + * 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_name + * 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 field_format($obj_type, $object, $field, $item, $formatter_name = NULL, $formatter_settings = array()) { + if (!is_array($field)) { + $field = field_info_field($field); + } + + if (field_access('view', $field)) { + // Basically, we need $field, $instance, $obj_type, $object to be able to display a value... + list(, , $bundle) = field_attach_extract_ids($obj_type, $object); + $instance = field_info_instance($field['field_name'], $bundle); + + $display = array( + 'type' => $formatter_name, + 'settings' => $formatter_settings, + ); + $display = _field_get_formatter($display, $field); + if ($display['type'] && $display['type'] !== 'hidden') { + $theme = $formatter['module'] .'_formatter_'. $display['type']; + + $element = array( + '#theme' => $theme, + '#field_name' => $field['field_name'], + '#bundle' => $bundle, + '#formatter' => $display['type'], + '#settings' => $display['settings'], + '#object' => $object, + '#delta' => isset($item['#delta']) ? $item['#delta'] : NULL, + ); + + if (field_behaviors_formatter('multiple values', $display) == FIELD_BEHAVIOR_DEFAULT) { + // Single value formatter. + + // hook_field('sanitize') expects an array of items, so we build one. + $items = array($item); + $function = $field['module'] .'_field_sanitize'; + if (function_exists($function)) { + $function($obj_type, $object, $field, $instance, $items); + } + + $element['#item'] = $items[0]; + } + else { + // Multiple values formatter. + $items = $item; + $function = $field['module'] .'_field_sanitize'; + if (function_exists($function)) { + $function($obj_type, $object, $field, $instance, $items); + } + + foreach ($items as $delta => $item) { + $element[$delta] = array( + '#item' => $item, + '#weight' => $delta, + ); + } + } + + return theme($theme, $element); + } + } +} + +/** + * Render a single field, fully themed with label and multiple values. + * + * To be used by third-party code (Views, Panels...) that needs to output + * an isolated field. Do *not* use inside node templates, use the + * $FIELD_NAME_rendered variables instead. + * + * By default, the field is displayed using the settings defined for the + * 'full' or 'teaser' contexts (depending on the value of the $teaser param). + * Set $node->build_mode to a different value to use a different context. + * + * Different settings can be specified by adjusting $field['display']. + * + * @param $field + * The field definition. + * @param $object + * The object containing the field to display. Must at least contain the id key, + * revision key (if applicable), bundle key, and the field data. + * @param $teaser + * Similar to hook_nodeapi('view') + * @return + * The themed output for the field. + */ +function field_view_field($obj_type, $object, $field, $instance, $teaser = FALSE) { + $output = ''; + if (isset($object->$field['field_name'])) { + $items = $object->$field['field_name']; + + // Use 'full'/'teaser' if not specified otherwise. + $object->build_mode = isset($object->build_mode) ? $object->build_mode : NODE_BUILD_NORMAL; + + // One-field equivalent to _field_invoke('sanitize'). + $function = $field['module'] .'_field_sanitize'; + if (drupal_function_exists($function)) { + $function($obj_type, $object, $field, $instance, $items); + $object->$field['field_name'] = $items; + } + + $view = field_field_view($obj_type, $object, $field, $instance, $items, $teaser); + // TODO : what about hook_field_attach_view ? + + // field_field('view') adds a wrapper to handle variables and 'excluded' + // fields for node templates. We bypass it and render the actual field. + $output = drupal_render($view[$field['field_name']]['field']); + } + return $output; +} + +/** + * Determine whether the user has access to a given field. + * + * @param $op + * The operation to be performed. Possible values: + * - "edit" + * - "view" + * @param $field + * The field on which the operation is to be performed. + * @param $account + * (optional) The account to check, if not given use currently logged in user. + * @return + * TRUE if the operation is allowed; + * FALSE if the operation is denied. + */ +function field_access($op, $field, $account = NULL) { + global $user; + + if (is_null($account)) { + $account = $user; + } + + $field_access = module_invoke_all('field_access', $op, $field, $account); + foreach ($field_access as $value) { + if ($value === FALSE) { + return FALSE; + } + } + return TRUE; +} + +/** + * Theme preprocess function for field.tpl.php. + * + * The $variables array contains the following arguments: + * - $object + * - $field + * - $items + * - $teaser + * - $page + * + * @see field.tpl.php + */ +function template_preprocess_field(&$variables) { + $element = $variables['element']; + list(, , $bundle) = field_attach_extract_ids($element['#object_type'], $element['#object']); + $instance = field_info_instance($element['#field_name'], $bundle); + $field = field_info_field($element['#field_name']); + + $variables['object'] = $element['#object']; + $variables['field'] = $field; + $variables['instance'] = $instance; + $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'] = (bool)menu_get_object(); + + $field_empty = TRUE; + + foreach ($variables['items'] as $delta => $item) { + if (!isset($item['view']) || (empty($item['view']) && (string)$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' => check_plain(t($instance['label'])), + 'label_display' => $element['#label_display'], + 'field_empty' => $field_empty, + 'template_files' => array( + 'field', + 'field-'. $element['#field_name'], + 'field-'. $bundle, + 'field-'. $element['#field_name'] .'-'. $bundle, + ), + ); + $variables = array_merge($variables, $additions); +} + +// TODO D7: needed ? +/** + * Helper function to identify inactive fields. + */ +function field_inactive_fields($type_name = NULL) { +// module_load_include('inc', 'field', 'includes/field.crud'); +// if (!empty($type_name)) { +// $param = array('type_name' => $type_name); +// $inactive = array($type_name => array()); +// } +// else { +// $param = array(); +// $inactive = array(); +// } +// $all = field_field_instance_read($param, TRUE); +// $active = array_keys(field_fields()); +// foreach ($all as $field) { +// if (!in_array($field['field_name'], $active)) { +// $inactive[$field['type_name']][$field['field_name']] = field_field_instance_expand($field); +// } +// } +// if (!empty($type_name)) { +// return $inactive[$type_name]; +// } +// return $inactive; +} === added file 'modules/field/field.node_form.inc' --- modules/field/field.node_form.inc 1970-01-01 00:00:00 +0000 +++ modules/field/field.node_form.inc 2009-01-15 16:50:16 +0000 @@ -0,0 +1,415 @@ + $field, + 'instance' => $instance, + ); + // TODO : why do we need this ? + $form['#cache'] = FALSE; + + // If no access to edit the field, skip widget processing and just set the value. + $access = field_access('edit', $field); + + if (!$access) { + $addition[$field_name] = array( + '#access' => $access, + '#type' => 'value', + '#value' => $items, + ); + return $addition; + } + + // Populate with default values if we're creating a new object. + if (empty($items) && empty($id)) { + switch (field_behaviors_widget('default value', $instance)) { + case FIELD_BEHAVIOR_CUSTOM: + $default_value_function = $instance['widget']['module'] .'_widget_default_value'; + break; + case FIELD_BEHAVIOR_DEFAULT: + $default_value_function = 'field_widget_default_value'; + break; + } + if (isset($default_value_function) && drupal_function_exists($default_value_function)) { + $items = $default_value_function($obj_type, $object, $field, $instance, $form, $form_state, 0); + } + } + + $form_element = array(); + + // If field module handles multiple values for this form element, + // and we are displaying an individual element, process the multiple value + // form. + if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { + $form_element = field_multiple_value_form($field, $instance, $items, $form, $form_state); + } + // If the widget is handling multiple values (e.g optionwidgets), + // or if we are displaying an individual element, just get a single form + // element and make it the $delta value. + else { + $delta = isset($get_delta) ? $get_delta : 0; + $function = $instance['widget']['module'] .'_field_widget'; + if (drupal_function_exists($function)) { + if ($element = $function($form, $form_state, $instance, $items, $delta)) { + $defaults = array( + '#required' => $get_delta > 0 ? FALSE : $instance['required'], + '#columns' => array_keys($field['columns']), + '#title' => check_plain(t($instance['label'])), + '#description' => field_filter_xss($instance['description']), + '#delta' => $delta, + '#field_name' => $field['field_name'], + '#bundle' => $instance['bundle'], + ); + $element = array_merge($element, $defaults); + // If we're processing a specific delta value for a field where the + // field 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 (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { + $form_element[$delta] = $element; + } + else { + $form_element = $element; + } + } + } + } + + if ($form_element) { + $defaults = array( + '#field_name' => $field['field_name'], + '#tree' => TRUE, + '#weight' => $instance['weight'], + '#access' => $access, + ); + + $addition[$field['field_name']] = array_merge($form_element, $defaults); + $form['#fields'][$field['field_name']]['form_path'] = array($field['field_name']); + } + + 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 field_multiple_value_form($field, $instance, $items, &$form, &$form_state) { + $field = field_info_field($instance['field_name']); + $field_name = $field['field_name']; + + switch ($field['cardinality']) { + case FIELD_CARDINALITY_UNLIMITED: + $filled_items = field_set_empty($field, $items); + $current_item_count = isset($form_state['item_count'][$field_name]) + ? $form_state['item_count'][$field_name] + : count($items); + // We always want at least one empty icon for the user to fill in. + $max = ($current_item_count > count($filled_items)) + ? $current_item_count - 1 + : $current_item_count; + + break; + default: + $max = $field['cardinality'] - 1; + break; + } + + $title = check_plain(t($instance['label'])); + $description = field_filter_xss(t($instance['description'])); + + $form_element = array( + '#theme' => 'field_multiple_value_form', + '#multiple' => $field['cardinality'], + '#title' => $title, + '#required' => $instance['required'], + '#description' => $description, + ); + + $function = $instance['widget']['module'] .'_field_widget'; + if (drupal_function_exists($function)) { + for ($delta = 0; $delta <= $max; $delta++) { + if ($element = $function($form, $form_state, $instance, $items, $delta)) { + $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; + $defaults = array( + '#title' => $multiple ? '' : $title, + '#description' => $multiple ? '' : $description, + '#required' => $delta == 0 && $instance['required'], + '#weight' => $delta, + '#delta' => $delta, + '#columns' => array_keys($field['columns']), + '#field_name' => $field_name, + '#bundle' => $instance['bundle'], + ); + + // Add an input field for the delta (drag-n-drop reordering), which will + // be hidden by tabledrag js behavior. + if ($multiple) { + // We name the element '_weight' to avoid clashing with column names + // defined by field modules. + $element['_weight'] = array( + '#type' => 'weight', + '#delta' => $max, // this 'delta' is the 'weight' element's property + '#default_value' => isset($items[$delta]['_weight']) ? $items[$delta]['_weight'] : $delta, + '#weight' => 100, + ); + } + + $form_element[$delta] = array_merge($element, $defaults); + } + } + + // Add AHAH add more button, if not working with a programmed form. + if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && empty($form['#programmed'])) { + // Make sure the form is cached so ahah can work. + $form['#cache'] = TRUE; + $bundle_name_url_str = str_replace('_', '-', $instance['bundle']); + $field_name_url_str = str_replace('_', '-', $field_name); + + $form_element[$field_name .'_add_more'] = array( + '#type' => 'submit', + '#name' => $field_name .'_add_more', + '#value' => t('Add another item'), + '#weight' => $instance['weight'] + $max + 1, + // Submit callback for disabled JavaScript. + '#submit' => array('field_add_more_submit'), + '#ahah' => array( + 'path' => 'field/js_add_more/'. $bundle_name_url_str .'/'. $field_name_url_str, + 'wrapper' => $field_name_url_str .'-items', + 'method' => 'replace', + 'effect' => 'fade', + ), + // When JS is disabled, the field_add_more_submit handler will find + // the relevant field using these entries. + '#field_name' => $field_name, + '#bundle' => $instance['bundle'], + ); + + // Add wrappers for the fields and 'more' button. + $form_element['#prefix'] = '
'; + $form_element[$field_name .'_add_more']['#prefix'] = '
'; + $form_element[$field_name .'_add_more']['#suffix'] = '
'; + } + } + return $form_element; +} + +/** + * Theme an individual form element. + * + * Combine multiple values into a table with drag-n-drop reordering. + * TODO : convert to a template. + */ +function theme_field_multiple_value_form($element) { + $output = ''; + + if ($element['#multiple'] > 1 || $element['#multiple'] == FIELD_CARDINALITY_UNLIMITED) { + $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' => $element['#title'], '!required' => $required)), + 'colspan' => 2 + ), + t('Order'), + ); + $rows = array(); + + // Sort items according to '_weight' (needed when the form comes back after + // preview or failed validation) + $items = array(); + foreach (element_children($element) as $key) { + if ($key !== $element['#field_name'] .'_add_more') { + $items[] = &$element[$key]; + } + } + usort($items, '_field_sort_items_value_helper'); + + // Add the items as table rows. + foreach ($items as $key => $item) { + $item['_weight']['#attributes']['class'] = $order_class; + $delta_element = drupal_render($item['_weight']); + $cells = array( + array('data' => '', 'class' => 'field-multiple-drag'), + drupal_render($item), + array('data' => $delta_element, 'class' => 'delta-order'), + ); + $rows[] = array( + 'data' => $cells, + 'class' => 'draggable', + ); + } + + $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'field-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); + } + else { + foreach (element_children($element) as $key) { + $output .= drupal_render($element[$key]); + } + } + + return $output; +} + +/** + * Submit handler to add more choices to a field 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 field_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']; + + // 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['values'][$field_name]); + } +} + +/** + * Menu callback for AHAH addition of new empty widgets. + */ +function field_add_more_js($bundle_name, $field_name) { + // Arguments are coming from the url, so we translate back dashes. + $field_name = str_replace('-', '_', $field_name); + + $invalid = FALSE; + if (empty($_POST['form_build_id'])) { + // Invalid request. + $invalid = TRUE; + } + + // Retrieve the cached form. + $form_state = array('submitted' => FALSE); + $form_build_id = $_POST['form_build_id']; + $form = form_get_cache($form_build_id, $form_state); + if (!$form) { + // Invalid form_build_id. + $invalid = TRUE; + } + + // Retrieve field information. + $field = $form['#fields'][$field_name]['field']; + $instance = $form['#fields'][$field_name]['instance']; + $form_path = $form['#fields'][$field_name]['form_path']; + // TODO D7 : abstract $_POST[$field_name] path too ? + if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) { + // Ivnalid + $invalid = TRUE; + } + + if ($invalid) { + drupal_json(array('data' => '')); + exit; + } + + // We don't simply return a new empty widget row to append to existing ones, + // because: + // - ahah.js won't simply let us add a new row to a table + // - attaching the 'draggable' behavior won't be easy + // So we resort to rebuilding the whole table of widgets including the + // existing ones, which makes us jump through a few hoops. + + // The form that we get from the cache is unbuilt. We need to build it so + // that _value callbacks can be executed and $form_state['values'] populated. + // We only want to affect $form_state['values'], not the $form itself + // (built forms aren't supposed to enter the cache) nor the rest of + // $form_state, so we use copies of $form and $form_state. + $form_copy = $form; + $form_state_copy = $form_state; + $form_copy['#post'] = array(); + form_builder($_POST['form_id'], $form_copy, $form_state_copy); + // Just grab the data we need. + $form_state['values'] = $form_state_copy['values']; + // Reset cached ids, so that they don't affect the actual form we output. + form_clean_id(NULL, TRUE); + + // Sort the $form_state['values'] we just built *and* the incoming $_POST data + // according to d-n-d reordering. + unset($form_state['values'][$field_name][$field['field_name'] .'_add_more']); + foreach ($_POST[$field_name] as $delta => $item) { + $form_state['values'][$field_name][$delta]['_weight'] = $item['_weight']; + } + $form_state['values'][$field_name] = _field_sort_items($field, $form_state['values'][$field_name]); + $_POST[$field_name] = _field_sort_items($field, $_POST[$field_name]); + + // Build our new form element for the whole field, asking for one more element. + $form_state['item_count'] = array($field_name => count($_POST[$field_name]) + 1); + $items = $form_state['values'][$field_name]; + $form_element = field_field_form(NULL, NULL, $field, $instance, $items, $form, $form_state); + // Let other modules alter it. + drupal_alter('form', $form_element, array(), 'field_add_more_js'); + + // Add the new element at the right location in the (original, unbuilt) form. + $target = &$form; + foreach ($form_path as $key) { + $target = &$target[$key]; + } + $target = $form_element[$field_name]; + + // Save the new definition of the form. + $form_state['values'] = array(); + form_set_cache($form_build_id, $form, $form_state); + + // Build the new form against the incoming $_POST values so that we can + // render the new element. + $delta = max(array_keys($_POST[$field_name])) + 1; + $_POST[$field_name][$delta]['_weight'] = $delta; + $form_state = array('submitted' => FALSE); + $form += array( + '#post' => $_POST, + '#programmed' => FALSE, + ); + $form = form_builder($_POST['form_id'], $form, $form_state); + + // Render the new output. + // We get fetch the form element from the built $form. + $field_form = $form; + foreach ($form_path as $key) { + $field_form = $field_form[$key]; + } + // We add a div around the new field 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'] : '') .'
'; + + // If a newly inserted widget contains AHAH behaviors, they normally won't + // work because AHAH doesn't know about those - it just attaches to the exact + // form elements that were initially specified in the Drupal.settings object. + // The new ones didn't exist then, so we need to update Drupal.settings + // by ourselves in order to let AHAH know about those new form elements. + $javascript = drupal_add_js(NULL, NULL); + $output_js = isset($javascript['setting']) ? '' : ''; + + $output = theme('status_messages') . drupal_render($field_form) . $output_js; + drupal_json(array('status' => TRUE, 'data' => $output)); + exit; +} === added file 'modules/field/field.test' --- modules/field/field.test 1970-01-01 00:00:00 +0000 +++ modules/field/field.test 2009-01-15 16:50:16 +0000 @@ -0,0 +1,979 @@ + t('Field Attach tests'), + 'description' => t("Test Field Attach API functions."), + 'group' => t('Field') + ); + } + + function setUp() { + parent::setUp('field_sql_storage', 'field', 'field_test'); + + $this->field_name = strtolower($this->randomName().'_field_name'); + $this->table = _field_sql_storage_tablename($this->field_name); + $this->revision_table = _field_sql_storage_revision_tablename($this->field_name); + $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4); + field_create_field($this->field); + $this->instance = array( + 'field_name' => $this->field_name, + 'bundle' => 'test_bundle', + 'label' => $this->randomName().'_label', + 'description' => $this->randomName().'_description', + 'weight' => mt_rand(0, 127), + 'settings' => array( + 'test_instance_setting' => $this->randomName(), + ), + 'widget' => array( + 'type' => 'test_field_widget', + 'label' => 'Test Field', + 'settings' => array( + 'test_widget_setting' => $this->randomName(), + ) + ) + ); + field_create_instance($this->instance); + } + + function testFieldAttachLoad() { + $entity_type = 'field_test_entity'; + $eid = 0; + + $columns = array('entity', 'entity_id', 'revision_id', 'delta', $this->field_name .'_value'); + + // Insert data for four revisions to the field revisions table + $query = db_insert($this->revision_table)->fields($columns); + for ($evid = 0; $evid < 4; ++$evid) { + $values[$evid] = array(); + // Note: we insert one extra value ('<=' instead of '<'). + for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) { + $value = mt_rand(0, 127); + $values[$evid][] = $value; + $query->values(array($entity_type, $eid, $evid, $delta, $value)); + } + } + $query->execute(); + + // Insert data for the "most current revision" into the field table + $query = db_insert($this->table)->fields($columns); + foreach ($values[0] as $delta => $value) { + $query->values(array($entity_type, $eid, 0, $delta, $value)); + } + $query->execute(); + + // Load the "most current revision" + $entity = field_test_entity_get_entity($eid, 0, $this->instance['bundle']); + field_attach_load($entity_type, array($eid => $entity)); + foreach ($values[0] as $delta => $value) { + if ($delta < $this->field['cardinality']) { + $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta is loaded correctly for current revision"); + } + else { + $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for current revision."); + } + } + + // Load every revision + for ($evid = 0; $evid < 4; ++$evid) { + $entity = field_test_entity_get_entity($eid, $evid, $this->instance['bundle']); + field_attach_load_revision($entity_type, array($eid => $entity)); + foreach ($values[$evid] as $delta => $value) { + if ($delta < $this->field['cardinality']) { + $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta for revision $evid is loaded correctly"); + } + else { + $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for revision $evid."); + } + } + } + } + + function testFieldAttachLoadMultiple() { + // TODO : test the 'multiple' aspect of load: + // define 2 bundles, 3 fields + // bundle1 gets instances of field1, field2 + // bundle2 gets instances of field1, field3 + // load 2 entities (one for each bundle) in a single load + // check that everything gets loaded ok. + } + + function testFieldAttachInsertAndUpdate() { + $entity_type = 'field_test_entity'; + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + + // Test insert. + $values = array(); + // Note: we try to insert one extra value ('<=' instead of '<'). + // TODO : test empty values filtering and "compression" (store consecutive deltas). + for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) { + $values[$delta]['value'] = mt_rand(0, 127); + } + $entity->{$this->field_name} = $rev_values[0] = $values; + field_attach_insert($entity_type, $entity); + + $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); + foreach ($values as $delta => $value) { + if ($delta < $this->field['cardinality']) { + $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is inserted correctly")); + } + else { + $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets inserted."); + } + } + + // Test update. + $entity = field_test_entity_get_entity(0, 1, $this->instance['bundle']); + $values = array(); + // Note: we try to update one extra value ('<=' instead of '<'). + for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) { + $values[$delta]['value'] = mt_rand(0, 127); + } + $entity->{$this->field_name} = $rev_values[1] = $values; + field_attach_update($entity_type, $entity); + $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); + foreach ($values as $delta => $value) { + if ($delta < $this->field['cardinality']) { + $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is updated correctly")); + } + else { + $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets updated."); + } + } + + // Check that data for both revisions are in the revision table. + // We make sure each value is stored correctly, then unset it. + // When an entire revision's values are unset (remembering that we + // put one extra value in $values per revision), unset the entire + // revision. Then, if $rev_values is empty at the end, all + // revision data was found. + $results = db_select($this->revision_table, 't')->fields('t')->execute(); + foreach ($results as $row) { + $this->assertEqual($row->{$this->field_name . '_value'}, $rev_values[$row->revision_id][$row->delta]['value'], "Value {$row->delta} for revision {$row->revision_id} stored correctly"); + unset($rev_values[$row->revision_id][$row->delta]); + if (count($rev_values[$row->revision_id]) == 1) { + unset($rev_values[$row->revision_id]); + } + } + $this->assertTrue(empty($rev_values), "All values for all revisions are stored in revision table {$this->revision_table}"); + + // Check that update leaves the field data untouched if $object has no + // $field_name key. + unset($entity->{$this->field_name}); + field_attach_update($entity_type, $entity); + $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); + foreach ($values as $delta => $value) { + if ($delta < $this->field['cardinality']) { + $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Update with no field_name entry leaves value $delta untouched")); + } + } + + // Check that update with an empty $object->$field_name empties the field. + $entity->{$this->field_name} = NULL; + field_attach_update($entity_type, $entity); + $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); + $this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field.")); + } + + // Test insert and update with missing or invalid fields. For the + // most part, these tests pass by not crashing or causing exceptions. + function testFieldAttachSaveMissingData() { + $entity_type = 'field_test_entity'; + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + + // Insert: Field is missing + field_attach_insert($entity_type, $entity); + $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); + $this->assertEqual($count, 0, 'Missing field results in no inserts'); + + // Insert: Field is NULL + $entity->{$this->field_name} = NULL; + field_attach_insert($entity_type, $entity); + $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); + $this->assertEqual($count, 0, 'NULL field results in no inserts'); + + // Add some real data + $entity->{$this->field_name} = array(0 => array('value' => 1)); + field_attach_insert($entity_type, $entity); + $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); + $this->assertEqual($count, 1, 'Field data saved'); + + // Update: Field is missing. Data should survive. + unset($entity->{$this->field_name}); + field_attach_update($entity_type, $entity); + $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); + $this->assertEqual($count, 1, 'Missing field leaves data in table'); + + // Update: Field is NULL Data should be wiped. + $entity->{$this->field_name} = NULL; + field_attach_update($entity_type, $entity); + $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); + $this->assertEqual($count, 0, 'NULL field leaves no data in table'); + } + + function testFieldAttachViewAndPreprocess() { + $entity_type = 'field_test_entity'; + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + + // Populate values to be displayed. + $values = array(); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$delta]['value'] = mt_rand(0, 127); + } + $entity->{$this->field_name} = $values; + + // Simple formatter, label displayed. + $formatter_setting = $this->randomName(); + $this->instance['display'] = array( + 'full' => array( + 'label' => 'above', + 'type' => 'field_test_default', + 'settings' => array( + 'test_formatter_setting' => $formatter_setting, + ) + ), + ); + field_update_instance($this->instance); + $entity->content = field_attach_view($entity_type, $entity); + $output = drupal_render($entity->content); + $variables = field_attach_preprocess($entity_type, $entity); + $variable = $this->instance['field_name'] . '_rendered'; + $this->assertTrue(isset($variables[$variable]), "Variable $variable is available in templates."); + $this->content = $output; + $this->assertRaw($this->instance['label'], "Label is displayed."); + $this->content = $variables[$variable]; + $this->assertRaw($this->instance['label'], "Label is displayed (template variable)."); + foreach ($values as $delta => $value) { + $this->content = $output; + $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); + $this->content = $variables[$variable]; + $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied (template variable)."); + } + + // Label hidden. + $this->instance['display']['full']['label'] = 'hidden'; + field_update_instance($this->instance); + $entity->content = field_attach_view($entity_type, $entity); + $output = drupal_render($entity->content); + $variables = field_attach_preprocess($entity_type, $entity); + $this->content = $output; + $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed."); + $this->content = $variables[$variable]; + $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed (template variable)."); + + // Field hidden. + $this->instance['display'] = array( + 'full' => array( + 'label' => 'above', + 'type' => 'hidden', + + ), + ); + field_update_instance($this->instance); + $entity->content = field_attach_view($entity_type, $entity); + $output = drupal_render($entity->content); + $variables = field_attach_preprocess($entity_type, $entity); + $this->assertTrue(isset($variables[$variable]), "Hidden field: variable $variable is available in templates."); + $this->content = $output; + $this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed."); + foreach ($values as $delta => $value) { + $this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed."); + } + + // Multiple formatter. + $formatter_setting = $this->randomName(); + $this->instance['display'] = array( + 'full' => array( + 'label' => 'above', + 'type' => 'field_test_multiple', + 'settings' => array( + 'test_formatter_setting_multiple' => $formatter_setting, + ) + ), + ); + field_update_instance($this->instance); + $entity->content = field_attach_view($entity_type, $entity); + $output = drupal_render($entity->content); + $variables = field_attach_preprocess($entity_type, $entity); + $display = $formatter_setting; + foreach ($values as $delta => $value) { + $display .= "|$delta:{$value['value']}"; + } + $this->content = $output; + $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied."); + $this->content = $variables[$variable]; + $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied (template variable)."); + + // TODO: + // - check that the 'exclude' option works (if we keep it in core) + // - check display order with several fields + } + + function testFieldAttachDelete() { + $entity_type = 'field_test_entity'; + $rev[0] = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + + // Create revision 0 + $values = array(); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$delta]['value'] = mt_rand(0, 127); + } + $rev[0]->{$this->field_name} = $values; + field_attach_insert($entity_type, $rev[0]); + + // Create revision 1 + $rev[1] = field_test_entity_get_entity(0, 1, $this->instance['bundle']); + $rev[1]->{$this->field_name} = $values; + field_attach_update($entity_type, $rev[1]); + + // Create revision 2 + $rev[2] = field_test_entity_get_entity(0, 2, $this->instance['bundle']); + $rev[2]->{$this->field_name} = $values; + field_attach_update($entity_type, $rev[2]); + + // Confirm each revision loads + foreach (array_keys($rev) as $vid) { + $read = field_test_entity_get_entity(0, $vid, $this->instance['bundle']); + field_attach_load_revision($entity_type, array(0 => $read)); + $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values."); + } + + // Delete revision 1, confirm the other two still load. + field_attach_delete_revision($entity_type, $rev[1]); + foreach (array(0, 2) as $vid) { + $read = field_test_entity_get_entity(0, $vid, $this->instance['bundle']); + field_attach_load_revision($entity_type, array(0 => $read)); + $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values."); + } + + // Confirm the current revision still loads + $read = field_test_entity_get_entity(0, 2, $this->instance['bundle']); + field_attach_load($entity_type, array(0 => $read)); + $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object current revision has {$this->field['cardinality']} values."); + + // Delete all field data, confirm nothing loads + field_attach_delete($entity_type, $rev[2]); + foreach (array(0, 1, 2) as $vid) { + $read = field_test_entity_get_entity(0, $vid, $this->instance['bundle']); + field_attach_load_revision($entity_type, array(0 => $read)); + $this->assertFalse(isset($read->{$this->field_name}), "The test object revision $vid is deleted."); + } + $read = field_test_entity_get_entity(0, 2, $this->instance['bundle']); + field_attach_load($entity_type, array(0 => $read)); + $this->assertFalse(isset($read->{$this->field_name}), "The test object current revision is deleted."); + } + + function testFieldAttachCreateRenameBundle() { + // Create a new bundle. This has to be initiated by the module so that its + // hook_fieldable_info() is consistent. + $new_bundle = 'test_bundle_' . strtolower($this->randomName()); + field_test_create_bundle($new_bundle, $this->randomName()); + + // Add an instance to that bundle. + $this->instance['bundle'] = $new_bundle; + field_create_instance($this->instance); + + // Save an object with data in the field. + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + $values = array(); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$delta]['value'] = mt_rand(0, 127); + } + $entity->{$this->field_name} = $values; + $entity_type = 'field_test_entity'; + field_attach_insert($entity_type, $entity); + + // Verify the field data is present on load. + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + field_attach_load($entity_type, array(0 => $entity)); + $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Data are retrieved for the new bundle"); + + // Rename the bundle. This has to be initiated by the module so that its + // hook_fieldable_info() is consistent. + $new_bundle = 'test_bundle_' . strtolower($this->randomName()); + field_test_rename_bundle($this->instance['bundle'], $new_bundle); + + // Check that the instance definition has been updated. + $this->instance = field_info_instance($this->field_name, $new_bundle); + $this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance."); + + // Verify the field data is present on load. + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + field_attach_load($entity_type, array(0 => $entity)); + $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Bundle name has been updated in the field storage"); + } + + function testFieldAttachDeleteBundle() { + // Create a new bundle. This has to be initiated by the module so that its + // hook_fieldable_info() is consistent. + $new_bundle = 'test_bundle_' . strtolower($this->randomName()); + field_test_create_bundle($new_bundle, $this->randomName()); + + // Add an instance to that bundle. + $this->instance['bundle'] = $new_bundle; + field_create_instance($this->instance); + + // Create a second field for the test bundle + $field_name = strtolower($this->randomName().'_field_name'); + $table = _field_sql_storage_tablename($field_name); + $revision_table = _field_sql_storage_revision_tablename($field_name); + $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 1); + field_create_field($field); + $instance = array( + 'field_name' => $field_name, + 'bundle' => $this->instance['bundle'], + 'label' => $this->randomName().'_label', + 'description' => $this->randomName().'_description', + 'weight' => mt_rand(0, 127), + // test_field has no instance settings + 'widget' => array( + 'type' => 'test_field_widget', + 'settings' => array( + 'size' => mt_rand(0, 255)))); + field_create_instance($instance); + + // Save an object with data for both fields + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + $values = array(); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$delta]['value'] = mt_rand(0, 127); + } + $entity->{$this->field_name} = $values; + $entity->{$field_name} = array(0 => array('value' => 99)); + $entity_type = 'field_test_entity'; + field_attach_insert($entity_type, $entity); + + // Verify the fields are present on load + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + field_attach_load($entity_type, array(0 => $entity)); + $this->assertEqual(count($entity->{$this->field_name}), 4, "First field got loaded"); + $this->assertEqual(count($entity->{$field_name}), 1, "Second field got loaded"); + + // Delete the bundle. This has to be initiated by the module so that its + // hook_fieldable_info() is consistent. + field_test_delete_bundle($this->instance['bundle']); + + // Verify no data gets loaded + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + field_attach_load($entity_type, array(0 => $entity)); + $this->assertFalse(isset($entity->{$this->field_name}), "No data for first field"); + $this->assertFalse(isset($entity->{$field_name}), "No data for second field"); + + // Verify that the instances are gone + $this->assertFalse(field_read_instance($this->field_name, $this->instance['bundle']), "First field is deleted"); + $this->assertFalse(field_read_instance($field_name, $instance['bundle']), "Second field is deleted"); + } + + function testFieldAttachCache() { + // Create a revision + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + $values = array(); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$delta]['value'] = mt_rand(0, 127); + } + $entity->{$this->field_name} = $values; + + $noncached_type = 'field_test_entity'; + $cached_type = 'field_test_cacheable_entity'; + + // Non-cached type: + $cid = "field:$noncached_type:0:0"; + + // Confirm no initial cache entry + $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no initial cache entry'); + + // Save, and confirm no cache entry + field_attach_insert($noncached_type, $entity); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on save'); + + // Load, and confirm no cache entry + field_attach_load($noncached_type, array(0 => $entity)); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on load'); + + // Cached type: + $cid = "field:$cached_type:0:0"; + + // Confirm no initial cache entry + $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no initial cache entry'); + + // Save, and confirm no cache entry + field_attach_insert($cached_type, $entity); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save'); + + // Load, and confirm cache entry + field_attach_load($cached_type, array(0 => $entity)); + $cache = cache_get($cid, 'cache_field'); + $this->assertEqual($cache->data[$this->field_name], $values, 'Cached: correct cache entry on load'); + + // Delete, and confirm no cache entry + field_attach_delete($cached_type, $entity); + $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save'); + } + + // Verify that field_attach_validate() invokes the correct + // hook_field_validate. NOTE: This tests the FAPI-connected + // behavior of hook_field_validate. As discussed at + // http://groups.drupal.org/node/18019, field validation will + // eventually be disconnected from FAPI, at which point this + // function will have to be rewritten. + function testFieldAttachValidate() { + $entity_type = 'field_test_entity'; + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + + // Set up values to generate errors + $values = array(); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$delta]['value'] = mt_rand(1, 127); + $values[$delta]['_error_element'] = 'field_error_'.$delta; + } + // Arrange for item 1 not to generate an error + $values[1]['value'] = 1; + $entity->{$this->field_name} = $values; + + field_attach_validate($entity_type, $entity, array()); + + $errors = form_get_errors(); + foreach ($values as $delta => $value) { + if ($value['value'] != 1) { + $this->assertTrue(isset($errors[$value['_error_element']]), "Error is set on {$value['_error_element']}: {$errors[$value['_error_element']]}"); + unset($errors[$value['_error_element']]); + } + else { + $this->assertFalse(isset($errors[$value['_error_element']]), "Error is not set on {$value['_error_element']}"); + } + } + $this->assertEqual(count($errors), 0, 'No extraneous form errors set'); + } + + // Validate that FAPI elements are generated. This could be much + // more thorough, but it does verify that the correct widgets show up. + function testFieldAttachForm() { + $entity_type = 'field_test_entity'; + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + + $form = $form_state = array(); + field_attach_form($entity_type, $entity, $form, $form_state); + + $this->assertEqual($form[$this->field_name]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}"); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + // field_test_widget uses 'textfield' + $this->assertEqual($form[$this->field_name][$delta]['#type'], 'textfield', "Form delta $delta widget is textfield"); + } + } + + function testFieldAttachPresave() { + $entity_type = 'field_test_entity'; + $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']); + + $values = array(); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$delta]['value'] = mt_rand(1, 127); + } + // field_test fields are empty if empty() + $values[1]['value'] = 0; + $entity->{$this->field_name} = $values; + + field_attach_presave($entity_type, $entity); + + unset($values[1]); + $values = array_merge($values); + $this->assertIdentical($entity->{$this->field_name}, $values, 'Presave filters empty values'); + } +} + +class FieldInfoTestCase extends DrupalWebTestCase { + + function getInfo() { + return array( + 'name' => t('Field info tests'), + 'description' => t("Get information about existing fields, instances and bundles."), + 'group' => t('Field') + ); + } + + function setUp() { + parent::setUp('field_sql_storage', 'field', 'field_test'); + } + + function testFieldInfo() { + // Test that field_test module's fields, widgets, and formatters show up. + $field_test_info = field_test_field_info(); + $formatter_info = field_test_field_formatter_info(); + $widget_info = field_test_field_widget_info(); + + $info = field_info_field_types(); + foreach ($field_test_info as $t_key => $field_type) { + foreach ($field_type as $key => $val) { + $this->assertEqual($info[$t_key][$key], $val, t("Field type $t_key key $key is $val")); + } + $this->assertEqual($info[$t_key]['module'], 'field_test', t("Field type field_test module appears")); + } + + $info = field_info_formatter_types(); + foreach ($formatter_info as $f_key => $formatter) { + foreach ($formatter as $key => $val) { + $this->assertEqual($info[$f_key][$key], $val, t("Formatter type $f_key key $key is $val")); + } + $this->assertEqual($info[$f_key]['module'], 'field_test', t("Formatter type field_test module appears")); + } + + $info = field_info_widget_types(); + foreach ($widget_info as $w_key => $widget) { + foreach ($widget as $key => $val) { + $this->assertEqual($info[$w_key][$key], $val, t("Widget type $w_key key $key is $val")); + } + $this->assertEqual($info[$w_key]['module'], 'field_test', t("Widget type field_test module appears")); + } + + // Verify that no fields or instances exist + $fields = field_info_fields(); + $instances = field_info_instances(FIELD_TEST_BUNDLE); + $this->assertTrue(empty($fields), t('With no fields, info fields is empty.')); + $this->assertTrue(empty($instances), t('With no instances, info bundles is empty.')); + + // Create a field, verify it shows up. + $field = array( + 'field_name' => strtolower($this->randomName()), + 'type' => 'test_field', + ); + field_create_field($field); + $fields = field_info_fields(); + $this->assertEqual(count($fields), 1, t('One field exists')); + $this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], t('info fields contains field name')); + $this->assertEqual($fields[$field['field_name']]['type'], $field['type'], t('info fields contains field type')); + $this->assertEqual($fields[$field['field_name']]['module'], 'field_test', t('info fields contains field module')); + $settings = array('test_field_setting' => 'dummy test string'); + foreach ($settings as $key => $val) { + $this->assertEqual($fields[$field['field_name']]['settings'][$key], $val, t("Field setting $key has correct default value $val")); + } + $this->assertEqual($fields[$field['field_name']]['cardinality'], 1, t('info fields contains cardinality 1')); + $this->assertEqual($fields[$field['field_name']]['active'], 1, t('info fields contains active 1')); + + // Create an instance, verify that it shows up + $instance = array( + 'field_name' => $field['field_name'], + 'bundle' => FIELD_TEST_BUNDLE, + 'label' => $this->randomName(), + 'description' => $this->randomName(), + 'weight' => mt_rand(0, 127), + // test_field has no instance settings + 'widget' => array( + 'type' => 'test_field_widget', + 'settings' => array( + 'test_setting' => 999))); + field_create_instance($instance); + + $instances = field_info_instances($instance['bundle']); + $this->assertEqual(count($instances), 1, t('One instance shows up in info when attached to a bundle.')); + $this->assertTrue($instance < $instances[$instance['field_name']], t('Instance appears in info correctly')); + } + + // Test that the field_info settings convenience functions work + function testSettingsInfo() { + $info = field_test_field_info(); + foreach ($info as $type => $data) { + $this->assertIdentical(field_info_field_settings($type), $data['settings'], "field_info_field_settings returns {$type}'s field settings"); + $this->assertIdentical(field_info_instance_settings($type), $data['instance_settings'], "field_info_field_settings returns {$type}'s field instance settings"); + } + + $info = field_test_field_widget_info(); + foreach ($info as $type => $data) { + $this->assertIdentical(field_info_widget_settings($type), $data['settings'], "field_info_widget_settings returns {$type}'s widget settings"); + } + + $info = field_test_field_formatter_info(); + foreach ($info as $type => $data) { + $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings"); + } + } +} + +class FieldTestCase extends DrupalWebTestCase { + function getInfo() { + return array( + 'name' => t('Field tests'), + 'description' => t("Create / read /update a field."), + 'group' => t('Field') + ); + } + + function setUp() { + parent::setUp('field_sql_storage', 'field', 'field_test'); + } + + // TODO : test creation with + // - a full fledged $field structure, check that all the values are there + // - a minimal $field structure, check all default values are set + // defer actual $field comparison to a helper function, used for the two cases above + /** + * Test the creation of a field. + */ + function testCreateField() { + $field_definition = array( + 'field_name' => strtolower($this->randomName()), + 'type' => 'test_field', + ); + field_create_field($field_definition); + + $field = field_read_field($field_definition['field_name']); + + // Ensure that basic properties are preserved. + $this->assertEqual($field['field_name'], $field_definition['field_name'], t('The field name is properly saved.')); + $this->assertEqual($field['type'], $field_definition['type'], t('The field type is properly saved.')); + + // Ensure that cardinality defaults to 1. + $this->assertEqual($field['cardinality'], 1, t('Cardinality defaults to 1.')); + + // Ensure that default settings are present. + $info = field_info_field_types($field['type']); + $settings = $info['settings']; + $this->assertIdentical($settings, $field['settings'] , t('Default field settings have been written.')); + + // Check that a table has been created for the field. + $this->assertTrue(db_table_exists('field_data_' . $field_definition['field_name']), t('A table has been created for the field.')); + + // Guarantee that the name is unique. + try { + field_create_field($field_definition); + $this->fail(t('Cannot create two fields with the same name.')); + } catch (FieldException $e) { + $this->pass(t('Cannot create two fields with the same name.')); + } + + // Check that invalid field names are rejected. + $field_definition['field_name'] += '_#'; + try { + field_create_field($field_definition); + $this->fail(t('Cannot create a field with an invalid name.')); + } catch (FieldException $e) { + $this->pass(t('Cannot create a field with an invalid name.')); + } + + // TODO : other failures + } + + function testReadField() { + + } + + /** + * Test the deletion of a field. + */ + function testDeleteField() { + // TODO: Also test deletion of the data stored in the field ? + + // Create two fields (so we can test that only one is deleted). + $this->field = $this->drupalCreateField('test_field', 'test_field_name'); + $this->another_field = $this->drupalCreateField('test_field', 'another_test_field_name'); + + // Create instances for each. + $this->instance_definition = array( + 'field_name' => $this->field['field_name'], + 'bundle' => FIELD_TEST_BUNDLE, + 'widget' => array( + 'type' => 'test_field_widget', + ), + ); + field_create_instance($this->instance_definition); + $this->another_instance_definition = $this->instance_definition; + $this->another_instance_definition['field_name'] = $this->another_field['field_name']; + field_create_instance($this->another_instance_definition); + + // Test that the first field is not deleted, and then delete it. + $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE)); + $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field is not marked for deletion.')); + field_delete_field($this->field['field_name']); + + // Make sure that the field is marked as deleted when it is specifically + // loaded. + $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE)); + $this->assertTrue(!empty($field['deleted']), t('A deleted field is marked for deletion.')); + + // Make sure that this field's instance is marked as deleted when it is + // specifically loaded. + $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE)); + $this->assertTrue(!empty($instance['deleted']), t('An instance for a deleted field is marked for deletion.')); + + // Try to load the field normally and make sure it does not show up. + $field = field_read_field($this->field['field_name']); + $this->assertTrue(empty($field), t('A deleted field is not loaded by default.')); + + // Try to load the instance normally and make sure it does not show up. + $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']); + $this->assertTrue(empty($instance), t('An instance for a deleted field is not loaded by default.')); + + // Make sure the other field (and its field instance) are not deleted. + $another_field = field_read_field($this->another_field['field_name']); + $this->assertTrue(!empty($another_field) && empty($another_field['deleted']), t('A non-deleted field is not marked for deletion.')); + $another_instance = field_read_instance($this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']); + $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('An instance of a non-deleted field is not marked for deletion.')); + } +} + +class FieldInstanceTestCase extends DrupalWebTestCase { + protected $field; + + function getInfo() { + return array( + 'name' => t('Field instance tests'), + 'description' => t("Create field entities by attaching fields to entities."), + 'group' => t('Field') + ); + } + + function setUp() { + parent::setUp('field_sql_storage', 'field', 'field_test'); + + $this->field = $this->drupalCreateField('test_field'); + $this->instance_definition = array( + 'field_name' => $this->field['field_name'], + 'bundle' => FIELD_TEST_BUNDLE, + ); + } + + // TODO : test creation with + // - a full fledged $instance structure, check that all the values are there + // - a minimal $instance structure, check all default values are set + // defer actual $instance comparison to a helper function, used for the two cases above, + // and for testUpdateFieldInstance + function testCreateFieldInstance() { + field_create_instance($this->instance_definition); + $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']); + $field_type = field_info_field_types($this->field['type']); + + // Check that default values are set. + $this->assertIdentical($instance['required'], FALSE, t('Required defaults to false.')); + $this->assertIdentical($instance['label'], $this->instance_definition['field_name'], t('Label defaults to field name.')); + $this->assertIdentical($instance['description'], '', t('Description defaults to empty string.')); + + // Check that default instance settings are set. + $settings = array('test_instance_setting' => 'dummy test string'); + $this->assertIdentical($settings, $instance['settings'] , t('Default instance settings have been written.')); + // Check that the widget is the default one. + $this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], t('Default widget has been written.')); + // Check that default widget settings are set. + $settings = array('test_widget_setting' => 'dummy test string'); + $this->assertIdentical($settings, $instance['widget']['settings'] , t('Default widget settings have been written.')); + // Check that we have display info for 'full' build_mode. + $this->assertTrue(isset($instance['display']['full']), t('Display for "full" build_mode has been written.')); + // Check that the formatter is the default one. + $this->assertIdentical($instance['display']['full']['type'], $field_type['default_formatter'], t('Default formatter for "full" build_mode has been written.')); + // Check that the default formatter settings are set. + $info = field_info_formatter_types($instance['display']['full']['type']); + $settings = $info['settings']; + $this->assertIdentical($settings, $instance['display']['full']['settings'] , t('Default formatter settings for "full" build_mode have been written.')); + + // Guarantee that the field/bundle combination is unique. + try { + field_create_instance($this->instance_definition); + $this->fail(t('Cannot create two instances with the same field / bundle combination.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot create two instances with the same field / bundle combination.')); + } + + // Check that the specified field exists. + try { + $this->instance_definition['field_name'] = $this->randomName(); + field_create_instance($this->instance_definition); + $this->fail(t('Cannot create an instance of a non-existing field.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot create an instance of a non-existing field.')); + } + + // TODO: test other failures. + } + + function testReadFieldInstance() { + + } + + function testUpdateFieldInstance() { + field_create_instance($this->instance_definition); + $field_type = field_info_field_types($this->field['type']); + + // Check that basic changes are saved. + $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']); + $instance['required'] = !$instance['required']; + $instance['weight']++; + $instance['label'] = $this->randomName(); + $instance['description'] = $this->randomName(); + $instance['settings']['test_instance_setting'] = $this->randomName(); + $instance['widget']['settings']['test_widget_setting'] =$this->randomName(); + $instance['display']['full']['settings']['test_formatter_setting'] = $this->randomName(); + field_update_instance($instance); + + $instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']); + $this->assertEqual($instance['required'], $instance_new['required'], t('"required" change is saved')); + $this->assertEqual($instance['weight'], $instance_new['weight'], t('"weight" change is saved')); + $this->assertEqual($instance['label'], $instance_new['label'], t('"label" change is saved')); + $this->assertEqual($instance['description'], $instance_new['description'], t('"description" change is saved')); + $this->assertEqual($instance['widget']['settings']['test_widget_setting'], $instance_new['widget']['settings']['test_widget_setting'], t('Widget setting change is saved')); + $this->assertEqual($instance['display']['full']['settings']['test_formatter_setting'], $instance_new['display']['full']['settings']['test_formatter_setting'], t('Formatter setting change is saved')); + + // Check that changing widget and formatter types updates the default settings. + $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']); + $instance['widget']['type'] = 'test_field_widget_multiple'; + $instance['display']['full']['type'] = 'field_test_multiple'; + field_update_instance($instance); + + $instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']); + $this->assertEqual($instance['widget']['type'], $instance_new['widget']['type'] , t('Widget type change is saved.')); + $settings = (array)module_invoke($instance_new['widget']['module'], 'widget_settings', $instance_new['widget']['type']); + $this->assertIdentical($settings, array_intersect_key($instance_new['widget']['settings'], $settings) , t('Widget type change updates default settings.')); + $this->assertEqual($instance['display']['full']['type'], $instance_new['display']['full']['type'] , t('Formatter type change is saved.')); + $info = field_info_formatter_types($instance_new['display']['full']['type']); + $settings = $info['settings']; + $this->assertIdentical($settings, array_intersect_key($instance_new['display']['full']['settings'], $settings) , t('Changing formatter type updates default settings.')); + + // Check that adding a new build mode is saved and gets default settings. + $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']); + $instance['display']['teaser'] = array(); + field_update_instance($instance); + + $instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']); + $this->assertTrue(isset($instance_new['display']['teaser']), t('Display for the new build_mode has been written.')); + $this->assertIdentical($instance_new['display']['teaser']['type'], $field_type['default_formatter'], t('Default formatter for the new build_mode has been written.')); + $info = field_info_formatter_types($instance_new['display']['teaser']['type']); + $settings = $info['settings']; + $this->assertIdentical($settings, $instance_new['display']['teaser']['settings'] , t('Default formatter settings for the new build_mode have been written.')); + + // TODO: test failures. + } + + function testDeleteFieldInstance() { + // TODO: Test deletion of the data stored in the field also. + // Need to check that data for a 'deleted' field / instance doesn't get loaded + // Need to check data marked deleted is cleaned on cron (not implemented yet...) + + // Create two instances for the same field so we can test that only one + // is deleted. + field_create_instance($this->instance_definition); + $this->another_instance_definition = $this->instance_definition; + $this->another_instance_definition['bundle'] .= '_another_bundle'; + field_create_instance($this->another_instance_definition); + + // Test that the first instance is not deleted, and then delete it. + $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE)); + $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new field instance is not marked for deletion.')); + field_delete_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']); + + // Make sure the instance is marked as deleted when the instance is + // specifically loaded. + $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE)); + $this->assertTrue(!empty($instance['deleted']), t('A deleted field instance is marked for deletion.')); + + // Try to load the instance normally and make sure it does not show up. + $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']); + $this->assertTrue(empty($instance), t('A deleted field instance is not loaded by default.')); + + // Make sure the other field instance is not deleted. + $another_instance = field_read_instance($this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']); + $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('A non-deleted field instance is not marked for deletion.')); + } +} === added directory 'modules/field/modules' === added directory 'modules/field/modules/field_sql_storage' === added file 'modules/field/modules/field_sql_storage/field_sql_storage.info' --- modules/field/modules/field_sql_storage/field_sql_storage.info 1970-01-01 00:00:00 +0000 +++ modules/field/modules/field_sql_storage/field_sql_storage.info 2009-01-15 16:50:15 +0000 @@ -0,0 +1,7 @@ +; $Id$ +name = Field SQL storage +description = Stores field data in an SQL database. +package = Core - fields +core = 7.x +files[] = field_sql_storage.module +files[] = field_sql_storage.install === added file 'modules/field/modules/field_sql_storage/field_sql_storage.install' --- modules/field/modules/field_sql_storage/field_sql_storage.install 1970-01-01 00:00:00 +0000 +++ modules/field/modules/field_sql_storage/field_sql_storage.install 2009-01-15 16:50:15 +0000 @@ -0,0 +1,32 @@ + 'Data storage for field '. $field['field_name'], + 'fields' => array( + 'entity' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'The entity type this data is attached to', + ), + 'bundle' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance', + ), + 'deleted' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'A boolean indicating whether this data item has been deleted' + ), + 'entity_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'The entity id this data is attached to', + ), + 'revision_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + 'description' => 'The entity revision id this data is attached to, or NULL if the entity type is not versioned', + ), + 'delta' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'The sequence number for this data item, used for multi-value fields', + ), + ), + 'primary key' => array('entity', 'entity_id', 'deleted', 'delta'), + // TODO : index on 'bundle' + ); + + // Add field columns. + foreach ($field['columns'] as $attributes) { + $column_name = $attributes['column']; + unset($attributes['column']); + $current['fields'][$column_name] = $attributes; + } + + // Construct the revision table. The primary key includes + // revision_id but not entity_id so that multiple revision loads can + // use the IN operator. + $revision = $current; + $revision['description'] = 'Revision archive storage for field '. $field['field_name']; + $revision['revision_id']['description'] = 'The entity revision id this data is attached to'; + $revision['primary key'] = array('entity', 'revision_id', 'deleted', 'delta'); + + return array( + _field_sql_storage_tablename($field['field_name']) => $current, + _field_sql_storage_revision_tablename($field['field_name']) => $revision, + ); +} + +function field_sql_storage_create_table($field) { + $schema = _field_sql_storage_schema($field); + foreach ($schema as $name => $table) { + db_create_table($ret, $name, $table); + } +} + +function field_sql_storage_delete_table($field_name) { + // Mark all data associated with the field for deletion. + $table = _field_sql_storage_tablename($field_name); + db_update($table) + ->fields(array('deleted' => 1)) + ->execute(); +} + +/** + * Load field data for a set of objects from the database. + * + * @param $obj_type + * The entity type of objects being loaded, such as 'node' or + * 'user'. + * @param $objects + * The array of objects for which to load data. The objects + * referenced by this array will have the field data added to them + * directly. + * @param $age + * FIELD_LOAD_CURRENT to load the most recent revision for all + * fields, or FIELD_LOAD_REVISION to load the version indicated by + * each object. + * @return + * An array of field data for the objects, keyed by entity id, field + * name, and item delta number. + */ +function field_sql_storage_load($obj_type, $objects, $age) { + $load_current = $age == FIELD_LOAD_CURRENT; + + // Gather ids needed for each field. + $field_ids = array(); + $delta_count = array(); + foreach ($objects as $obj) { + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $obj); + foreach (field_info_instances($bundle) as $instance) { + $field_ids[$instance['field_name']][] = $load_current ? $id : $vid; + $delta_count[$id][$instance['field_name']] = 0; + } + } + + $additions = array(); + foreach ($field_ids as $field_name => $ids) { + $field = field_info_field($field_name); + $table = $load_current ? _field_sql_storage_tablename($field_name) : _field_sql_storage_revision_tablename($field_name); + + $results = db_select($table, 't') + ->fields('t') + ->condition('entity', $obj_type) + ->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN') + ->condition('deleted', 0) + ->orderBy('delta') + ->execute(); + + foreach ($results as $row) { + if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) { + $item = array(); + // For each column declared by the field, populate the item + // from the prefixed database column. + foreach ($field['columns'] as $column => $attributes) { + $item[$column] = $row->$attributes['column']; + } + + // Add the item to the field values for the entity. + $additions[$row->entity_id][$field_name][] = $item; + $delta_count[$row->entity_id][$field_name]++; + } + } + } + return $additions; +} + +function field_sql_storage_write($obj_type, $object, $update = FALSE) { + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + + $instances = field_info_instances($bundle); + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $table_name = _field_sql_storage_tablename($field_name); + $revision_name = _field_sql_storage_revision_tablename($field_name); + $field = field_read_field($field_name); + + // Leave the field untouched if $object comes with no $field_name property. + // Empty the field if $object->$field_name is NULL or an empty array. + + // Function property_exists() is slower, so we catch the more frequent cases + // where it's an empty array with the faster isset(). + if (isset($object->$field_name) || property_exists($object, $field_name)) { + // Delete and insert, rather than update, in case a value was added. + if ($update) { + db_delete($table_name)->condition('entity', $obj_type)->condition('entity_id', $id)->execute(); + if (isset($vid)) { + db_delete($revision_name)->condition('entity', $obj_type)->condition('entity_id', $id)->condition('revision_id', $vid)->execute(); + } + } + + if ($object->$field_name) { + // Prepare the multi-insert query. + $columns = array('entity', 'entity_id', 'revision_id', 'bundle', 'delta'); + foreach ($field['columns'] as $column => $attributes) { + $columns[] = $attributes['column']; + } + $query = db_insert($table_name)->fields($columns); + if (isset($vid)) { + $revision_query = db_insert($revision_name)->fields($columns); + } + + $delta_count = 0; + foreach ($object->$field_name as $delta => $item) { + $record = array( + 'entity' => $obj_type, + 'entity_id' => $id, + 'revision_id' => $vid, + 'bundle' => $bundle, + 'delta' => $delta, + ); + foreach ($field['columns'] as $column => $attributes) { + $record[$attributes['column']] = isset($item[$column]) ? $item[$column] : NULL; + } + $query->values($record); + if (isset($vid)) { + $revision_query->values($record); + } + + if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) { + break; + } + } + + // Execute the insert. + $query->execute(); + if (isset($vid)) { + $revision_query->execute(); + } + } + } + } +} + +/** + * Delete all field data for a single object. This function actually + * deletes the data from the database. + * + * @param $obj_type + * The entity type of the object being deleted, such as 'node' or + * 'user'. + * @param $object + * The object for which to delete field data. + */ +function field_sql_storage_delete($obj_type, $object) { + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + + $instances = field_info_instances($bundle); + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $table_name = _field_sql_storage_tablename($field_name); + $revision_name = _field_sql_storage_revision_tablename($field_name); + db_delete($table_name) + ->condition('entity', $obj_type) + ->condition('entity_id', $id) + ->execute(); + db_delete($revision_name) + ->condition('entity', $obj_type) + ->condition('entity_id', $id) + ->execute(); + } +} + +/** + * Delete field data for a single revision of a single object. + * Deleting the current (most recently written) revision is not + * allowed as has undefined results. This function actually deletes + * the data from the database. + * + * @param $obj_type + * The entity type of the object being deleted, such as 'node' or + * 'user'. + * @param $object + * The object for which to delete field data. + */ +function field_sql_storage_delete_revision($obj_type, $object) { + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + + if (isset($vid)) { + $instances = field_info_instances($bundle); + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $revision_name = _field_sql_storage_revision_tablename($field_name); + db_delete($revision_name) + ->condition('entity', $obj_type) + ->condition('entity_id', $id) + ->condition('revision_id', $vid) + ->execute(); + } + } +} + +function field_sql_storage_delete_instance($field_name, $bundle) { + // Mark all data associated with the field for deletion. + $table_name = _field_sql_storage_tablename($field_name); + $revision_name = _field_sql_storage_revision_tablename($field_name); + db_update($table_name) + ->fields(array('deleted' => 1)) + ->condition('bundle', $bundle) + ->execute(); + db_update($revision_name) + ->fields(array('deleted' => 1)) + ->condition('bundle', $bundle) + ->execute(); +} + +function field_sql_storage_rename_bundle($bundle_old, $bundle_new) { + $instances = field_info_instances($bundle_old); + foreach ($instances as $instance) { + $table_name = _field_sql_storage_tablename($instance['field_name']); + $revision_name = _field_sql_storage_revision_tablename($instance['field_name']); + db_update($table_name) + ->fields(array('bundle' => $bundle_new)) + ->condition('bundle', $bundle_old) + ->execute(); + db_update($revision_name) + ->fields(array('bundle' => $bundle_new)) + ->condition('bundle', $bundle_old) + ->execute(); + } +} \ No newline at end of file === added directory 'modules/field/modules/list' === added file 'modules/field/modules/list/list.info' --- modules/field/modules/list/list.info 1970-01-01 00:00:00 +0000 +++ modules/field/modules/list/list.info 2009-01-15 16:50:15 +0000 @@ -0,0 +1,8 @@ +; $Id$ +name = List +description = Defines list field types. Use with Optionwidgets to create selection lists. +dependencies[] = field +package = Core - fields +core = 7.x + +files[]=list.module === added file 'modules/field/modules/list/list.module' --- modules/field/modules/list/list.module 1970-01-01 00:00:00 +0000 +++ modules/field/modules/list/list.module 2009-01-15 16:50:16 +0000 @@ -0,0 +1,206 @@ + array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_list_key' => array( + 'arguments' => array('element' => NULL), + ), + ); +} + +/** + * Implementation of hook_field_info(). + */ +function list_field_info() { + return array( + 'list' => array( + 'label' => t('List'), + 'description' => t('This field stores numeric keys from key/value lists of allowed values where the key is a simple alias for the position of the value, i.e. 0|First option, 1|Second option, 2|Third option.'), + 'settings' => array('allowed_values' => '', 'allowed_values_function' => ''), + 'default_widget' => 'option_widgets_select', + 'default_formatter' => 'list_default', + ), + 'list_boolean' => array( + 'label' => t('Boolean'), + 'description' => t('This field stores simple on/off or yes/no options.'), + 'settings' => array('allowed_values' => '', 'allowed_values_function' => ''), + 'default_widget' => 'option_widgets_select', + 'default_formatter' => 'list_default', + ), + 'list_number' => array( + 'label' => t('List (numeric)'), + 'description' => t('This field stores keys from key/value lists of allowed numbers where the stored numeric key has significance and must be preserved, i.e. \'Lifetime in days\': 1|1 day, 7|1 week, 31|1 month.'), + 'settings' => array('allowed_values' => '', 'allowed_values_function' => ''), + 'default_widget' => 'option_widgets_select', + 'default_formatter' => 'list_default', + ), + 'list_text' => array( + 'label' => t('List (text)'), + 'description' => t('This field stores keys from key/value lists of allowed values where the stored key has significance and must be a varchar, i.e. \'US States\': IL|Illinois, IA|Iowa, IN|Indiana'), + 'settings' => array('allowed_values' => '', 'allowed_values_function' => ''), + 'default_widget' => 'option_widgets_select', + 'default_formatter' => 'list_default', + ), + ); +} + +/** + * Implementation of hook_field_schema(). + */ +function list_field_columns($field) { + switch ($field['type']) { + case 'list_text': + $columns = array( + 'value' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + ); + break; + case 'list_number': + $columns = array( + 'value' => array( + 'type' => 'float', + 'unsigned' => TRUE, + 'not null' => FALSE, + ), + ); + break; + default: + $columns = array( + 'value' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + ), + ); + break; + } + return $columns; +} + +/** + * Implementation of hook_field_validate(). + */ +function list_field_validate($obj_type, $object, $field, $instance, $items, $form) { + $allowed_values = list_allowed_values($field); + if (is_array($items)) { + foreach ($items as $delta => $item) { + $error_element = isset($item['_error_element']) ? $item['_error_element'] : ''; + if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']); + if (!empty($item['value'])) { + if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) { + form_set_error($error_element, t('%name: illegal value.', array('%name' => t($instance['label'])))); + } + } + } + } +} + +/** + * Implementation of hook_field_is_empty(). + */ +function list_field_is_empty($item, $field) { + if (empty($item['value']) && (string)$item['value'] !== '0') { + return TRUE; + } + return FALSE; +} + +/** + * Implementation of hook_field_formatter_info(). + */ +function list_field_formatter_info() { + return array( + 'list_default' => array( + 'label' => t('Default'), + 'field types' => array('list', 'list_boolean', 'list_text', 'list_number'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'list_key' => array( + 'label' => t('Key'), + 'field types' => array('list', 'list_boolean', 'list_text', 'list_number'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + ); +} + +/** + * Theme function for 'default' list field formatter. + */ +function theme_field_formatter_list_default($element) { + $field = field_info_field($element['#field_name']); + if (($allowed_values = list_allowed_values($field)) && isset($allowed_values[$element['#item']['value']])) { + return $allowed_values[$element['#item']['value']]; + } + // If no match was found in allowed values, fall back to the key. + return $element['#item']['safe']; +} + +/** + * Theme function for 'key' list field formatter. + */ +function theme_field_formatter_list_key($element) { + return $element['#item']['safe']; +} + +/** + * Create an array of the allowed values for this field. + * + * Used by number and text fields, expects to find either + * a function that will return the correct value, or a string + * with keys and labels separated with '|' and with each + * new value on its own line. + */ +// TODO Rework this to create a method of selecting plugable allowed values lists. +function list_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['settings']['allowed_values_function'])) { + $function = $field['settings']['allowed_values_function']; + if (drupal_function_exists($function)) { + $allowed_values[$field['field_name']] = $function($field); + } + } + + if (empty($allowed_values[$field['field_name']]) && isset($field['settings']['allowed_values'])) { + $list = explode("\n", $field['settings']['allowed_values']); + $list = array_map('trim', $list); + $list = array_filter($list, 'strlen'); + foreach ($list as $opt) { + // Sanitize the user input with a permissive filter. + $opt = field_filter_xss($opt); + if (strpos($opt, '|') !== FALSE) { + list($key, $value) = explode('|', $opt); + $allowed_values[$field['field_name']][$key] = (isset($value) && $value !=='') ? $value : $key; + } + else { + $allowed_values[$field['field_name']][$opt] = $opt; + } + } + } + return $allowed_values[$field['field_name']]; +} === added directory 'modules/field/modules/node_reference' === added file 'modules/field/modules/node_reference/node_reference.info' --- modules/field/modules/node_reference/node_reference.info 1970-01-01 00:00:00 +0000 +++ modules/field/modules/node_reference/node_reference.info 2009-01-15 16:50:15 +0000 @@ -0,0 +1,9 @@ +; $Id$ +name = Node Reference +description = Defines a field type for referencing one node from another. +dependencies[] = field +dependencies[] = text +dependencies[] = option_widgets +files[]=node_reference.module +package = Core - fields +core = 7.x === added file 'modules/field/modules/node_reference/node_reference.module' --- modules/field/modules/node_reference/node_reference.module 1970-01-01 00:00:00 +0000 +++ modules/field/modules/node_reference/node_reference.module 2009-01-15 16:50:15 +0000 @@ -0,0 +1,797 @@ + 'node_reference autocomplete', + 'page callback' => 'node_reference_autocomplete', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK + ); + return $items; +} + +/** + * Implementation of hook_theme(). + */ +function node_reference_theme() { + return array( + 'node_reference_select' => array( + 'arguments' => array('element' => NULL), + ), + 'node_reference_buttons' => array( + 'arguments' => array('element' => NULL), + ), + 'node_reference_autocomplete' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_node_reference_default' => array( + 'arguments' => array('element'), + ), + 'field_formatter_node_reference_plain' => array( + 'arguments' => array('element'), + ), + 'field_formatter_node_reference_full' => array( + 'arguments' => array('element'), + 'function' => 'theme_field_formatter_node_reference_node', + ), + 'field_formatter_node_reference_teaser' => array( + 'arguments' => array('element'), + 'function' => 'theme_field_formatter_node_reference_node', + ), + ); +} + +/** + * Implementation of hook_field_info(). + */ +function node_reference_field_info() { + return array( + 'node_reference' => array( + 'label' => t('Node reference'), + 'description' => t('This field stores the ID of a related node as an integer value.'), + 'settings' => array('referenceable_types'), + 'default_widget' => 'node_reference_autocomplete', + 'default_formatter' => 'node_reference_default', + ), + ); +} + +/** + * Implementation of hook_field_settings(). + */ +function node_reference_field_settings($op, $field) { + switch ($op) { + + case 'database columns': + $columns = array( + 'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE), + ); + return $columns; + + } +} + +/** + * Implementation of hook_field(). + */ +function node_reference_field($op, $node, $field, &$items, $teaser, $page) { + switch ($op) { + // When preparing a translation, load any translations of existing references. + case 'prepare translation': + $addition = array(); + $addition[$field['field_name']] = array(); + if (isset($node->translation_source->$field['field_name']) && is_array($node->translation_source->$field['field_name'])) { + foreach ($node->translation_source->$field['field_name'] as $key => $reference) { + $reference_node = node_load($reference['nid']); + // Test if the referenced node type is translatable and, if so, + // load translations if the reference is not for the current language. + // We can assume the translation module is present because it invokes 'prepare translation'. + if (translation_supported_type($reference_node->type) && !empty($reference_node->language) && $reference_node->language != $node->language && $translations = translation_node_get_translations($reference_node->tnid)) { + // If there is a translation for the current language, use it. + $addition[$field['field_name']][] = array( + 'nid' => isset($translations[$node->language]) ? $translations[$node->language]->nid : $reference['nid'], + ); + } + } + } + return $addition; + + case 'validate': + // Extract nids to check. + $ids = array(); + foreach ($items as $delta => $item) { + if (is_array($item) && !empty($item['nid']) && is_numeric($item['nid'])) { + $ids[] = $item['nid']; + } + } + $refs = _node_reference_potential_references($field, '', NULL, $ids); + foreach ($items as $delta => $item) { + if (is_array($item)) { + $error_element = isset($item['_error_element']) ? $item['_error_element'] : ''; + if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']); + if (!empty($item['nid']) && !isset($refs[$item['nid']])) { + form_set_error($error_element, t("%name: this post can't be referenced.", array('%name' => t($field['widget']['label'])))); + } + } + } + return $items; + } +} + +/** + * Implementation of hook_field_is_empty(). + */ +function node_reference_field_is_empty($item, $field) { + if (empty($item['nid'])) { + return TRUE; + } + return FALSE; +} + +/** + * Implementation of hook_field_formatter_info(). + */ +function node_reference_field_formatter_info() { + return array( + 'node_reference_default' => array( + 'label' => t('Title (link)'), + 'field types' => array('node_reference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'node_reference_plain' => array( + 'label' => t('Title (no link)'), + 'field types' => array('node_reference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'node_reference_full' => array( + 'label' => t('Full node'), + 'field types' => array('node_reference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'node_reference_teaser' => array( + 'label' => t('Teaser'), + 'field types' => array('node_reference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + ); +} + +/** + * Theme function for 'default' node_reference field formatter. + */ +function theme_field_formatter_node_reference_default($element) { + $output = ''; + if (!empty($element['#item']['nid']) && is_numeric($element['#item']['nid']) && ($title = _node_reference_titles($element['#item']['nid']))) { + $output = l($title, 'node/'. $element['#item']['nid']); + } + return $output; +} + +/** + * Theme function for 'plain' node_reference field formatter. + */ +function theme_field_formatter_node_reference_plain($element) { + $output = ''; + if (!empty($element['#item']['nid']) && is_numeric($element['#item']['nid']) && ($title = _node_reference_titles($element['#item']['nid']))) { + $output = check_plain($title); + } + return $output; +} + +/** + * Proxy theme function for 'full' and 'teaser' node_reference field formatters. + */ +function theme_field_formatter_node_reference_node($element) { + static $recursion_queue = array(); + $output = ''; + if (!empty($element['#item']['nid']) && is_numeric($element['#item']['nid'])) { + $node = $element['#node']; + $field = field_fields($element['#field_name'], $element['#type_name']); + // 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('node_reference_formatter_default', $element); + } + if ($referenced_node = node_load($element['#item']['nid'])) { + $referenced_node->referencing_node = $node; + $referenced_node->referencing_field = $field; + _node_reference_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 _node_reference_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_field_widget_info(). + * + * We need custom handling of multiple values for the node_reference_select + * widget because we need to combine them into a options list rather + * than display multiple elements. + * + * We will use the Field 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 node_reference_field_widget_info() { + return array( + 'node_reference_select' => array( + 'label' => t('Select list'), + 'field types' => array('node_reference'), + 'settings' => array('autocomplete_match'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'node_reference_buttons' => array( + 'label' => t('Check boxes/radio buttons'), + 'field types' => array('node_reference'), + 'settings' => array('autocomplete_match'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'node_reference_autocomplete' => array( + 'label' => t('Autocomplete text field'), + 'field types' => array('node_reference'), + 'settings' => array('autocomplete_match'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_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 node_reference and userreference). + */ +function node_reference_elements() { + return array( + 'node_reference_select' => array( + '#input' => TRUE, + '#columns' => array('uid'), '#delta' => 0, + '#process' => array('node_reference_select_process'), + ), + 'node_reference_buttons' => array( + '#input' => TRUE, + '#columns' => array('uid'), '#delta' => 0, + '#process' => array('node_reference_buttons_process'), + ), + 'node_reference_autocomplete' => array( + '#input' => TRUE, + '#columns' => array('name'), '#delta' => 0, + '#process' => array('node_reference_autocomplete_process'), + '#autocomplete_path' => FALSE, + ), + ); +} + +/** + * Implementation of hook_field_widget_settings(). + */ +function node_reference_field_widget_settings($op, $widget) { + switch ($op) { + case 'form': + $form = array(); + $match = isset($widget['autocomplete_match']) ? $widget['autocomplete_match'] : 'contains'; + if ($widget['type'] == 'node_reference_autocomplete') { + $form['autocomplete_match'] = array( + '#type' => 'select', + '#title' => t('Autocomplete matching'), + '#default_value' => $match, + '#options' => array( + 'starts_with' => t('Starts with'), + 'contains' => t('Contains'), + ), + '#description' => t('Select the method used to collect autocomplete suggestions. Note that Contains can cause performance issues on sites with thousands of nodes.'), + ); + } + else { + $form['autocomplete_match'] = array('#type' => 'hidden', '#value' => $match); + } + return $form; + } +} + +/** + * Implementation of hook_field_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. + * + * Field module will set the weight, field name and delta values + * for each form element. + * + * If there are multiple values for this field, the field 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 $instance + * the field instance 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 node_reference_field_widget(&$form, &$form_state, $instance, $items, $delta = 0) { + switch ($instance['widget']['type']) { + case 'node_reference_select': + $element = array( + '#type' => 'node_reference_select', + '#default_value' => $items, + ); + break; + + case 'node_reference_buttons': + $element = array( + '#type' => 'node_reference_buttons', + '#default_value' => $items, + ); + break; + + case 'node_reference_autocomplete': + $element = array( + '#type' => 'node_reference_autocomplete', + '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, + '#value_callback' => 'node_reference_autocomplete_value', + ); + break; + } + return $element; +} + +/** + * Value for a node_reference autocomplete element. + * + * Substitute in the node title for the node nid. + */ +function node_reference_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 $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function node_reference_select_process($element, $edit, $form_state, $form) { + // The node_reference_select widget doesn't need to create its own + // element, it can wrap around the optionwidgets_select element. + // This will create a new, nested instance of the field. + // 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'] : '', + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + '#field_name' => $element['#field_name'], + '#type_name' => $element['#type_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + if (empty($element[$field_key]['#element_validate'])) { + $element[$field_key]['#element_validate'] = array(); + } + array_unshift($element[$field_key]['#element_validate'], 'node_reference_optionwidgets_validate'); + 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 $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function node_reference_buttons_process($element, $edit, $form_state, $form) { + // The node_reference_select widget doesn't need to create its own + // element, it can wrap around the optionwidgets_select element. + // This will create a new, nested instance of the field. + // Add a validation step where the value can be unwrapped. + $field_key = $element['#columns'][0]; + $element[$field_key] = array( + '#type' => 'optionwidgets_buttons', + '#default_value' => isset($element['#value']) ? $element['#value'] : '', + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + '#field_name' => $element['#field_name'], + '#type_name' => $element['#type_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + if (empty($element[$field_key]['#element_validate'])) { + $element[$field_key]['#element_validate'] = array(); + } + array_unshift($element[$field_key]['#element_validate'], 'node_reference_optionwidgets_validate'); + 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 node_reference_autocomplete_process($element, $edit, $form_state, $form) { + + // The node_reference 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' => 'node_reference/autocomplete/'. $element['#field_name'], + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + '#field_name' => $element['#field_name'], + '#type_name' => $element['#type_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + if (empty($element[$field_key]['#element_validate'])) { + $element[$field_key]['#element_validate'] = array(); + } + array_unshift($element[$field_key]['#element_validate'], 'node_reference_autocomplete_validate'); + + // Used so that hook_field('validate') knows where to flag an error. + $element['_error_element'] = array( + '#type' => 'value', + // Wrapping the element around a text_textfield element creates a + // nested element, so the final id will look like 'field-name-0-nid-nid'. + '#value' => implode('][', array_merge($element['#parents'], array($field_key, $field_key))), + ); + return $element; +} + +/** + * Validate a select/buttons element. + * + * Remove the wrapper layer and set the right element's value. + * We don't know exactly where this element is, so we drill down + * through the element until we get to our key. + * + * We use $form_state['values'] instead of $element['#value'] + * to be sure we have the most accurate value when other modules + * like optionwidgets are using #element_validate to alter the value. + */ +function node_reference_optionwidgets_validate($element, &$form_state) { + $field_key = $element['#columns'][0]; + + $value = $form_state['values']; + $new_parents = array(); + foreach ($element['#parents'] as $parent) { + $value = $value[$parent]; + // Use === to be sure we get right results if parent is a zero (delta) value. + if ($parent === $field_key) { + $element['#parents'] = $new_parents; + form_set_value($element, $value, $form_state); + break; + } + $new_parents[] = $parent; + } +} + +/** + * Validate an autocomplete element. + * + * Remove the wrapper layer and set the right element's value. + * This will move the nested value at 'field-name-0-nid-nid' + * back to its original location, 'field-name-0-nid'. + */ +function node_reference_autocomplete_validate($element, &$form_state) { + $field_name = $element['#field_name']; + $type_name = $element['#type_name']; + $field = field_fields($field_name, $type_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:n]. + list(, $title, $nid) = $matches; + if (!empty($title) && ($n = node_load($nid)) && $title != $n->title) { + form_error($element[$field_key], t('%name: title mismatch. Please check your selection.', array('%name' => t($field['widget']['label'])))); + } + } + else { + // No explicit nid. + $reference = _node_reference_potential_references($field, $value, 'equals', NULL, 1); + if (empty($reference)) { + form_error($element[$field_key], t('%name: found no valid post with that title.', array('%name' => t($field['widget']['label'])))); + } + else { + // 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... + $nid = key($reference); + } + } + } + form_set_value($element, $nid, $form_state); +} + +/** + * Implementation of hook_allowed_values(). + */ +function node_reference_allowed_values($field) { + $references = _node_reference_potential_references($field); + + $options = array(); + foreach ($references as $key => $value) { + // Views theming runs check_plain (htmlentities) on the values. + // We reverse that with html_entity_decode. + $options[$key] = html_entity_decode(strip_tags($value['rendered']), ENT_QUOTES); + } + return $options; +} + +/** + * Fetch an array of all candidate referenced nodes. + * + * This info is used in various places (aloowed values, autocomplete results, + * input validation...). Some of them only need the nids, others nid + titles, + * others yet nid + titles + rendered row (for display in widgets). + * The array we return contains all the potentially needed information, and lets + * consumers use the parts they actually need. + * + * @param $field + * The field description. + * @param $string + * Optional string to filter titles on (used by autocomplete). + * @param $match + * Operator to match filtered name against, can be any of: + * 'contains', 'equals', 'starts_with' + * @param $ids + * Optional node ids to lookup (the $string and $match arguments will be + * ignored). + * @param $limit + * If non-zero, limit the size of the result set. + * + * @return + * An array of valid nodes in the form: + * array( + * nid => array( + * 'title' => The node title, + * 'rendered' => The text to display in widgets (can be HTML) + * ), + * ... + * ) + */ +function _node_reference_potential_references($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { + static $results = array(); + + // Create unique id for static cache. + $cid = $field['field_name'] .':'. $match .':'. ($string !== '' ? $string : implode('-', $ids)) .':'. $limit; + if (!isset($results[$cid])) { + $references = FALSE; + if ($references === FALSE) { + $references = _node_reference_potential_references_standard($field, $string, $match, $ids, $limit); + } + + // Store the results. + $results[$cid] = $references; + } + + return $results[$cid]; +} + +/** + * Helper function for _node_reference_potential_references(): + * referenceable nodes defined by content types. + */ +function _node_reference_potential_references_standard($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { + $related_types = array(); + $where = array(); + $args = array(); + + if (is_array($field['referenceable_types'])) { + foreach (array_filter($field['referenceable_types']) as $related_type) { + $related_types[] = "n.type = '%s'"; + $args[] = $related_type; + } + } + + $where[] = implode(' OR ', $related_types); + + if (!count($related_types)) { + return array(); + } + + if ($string !== '') { + $match_operators = array( + 'contains' => "LIKE '%%%s%%'", + 'equals' => "= '%s'", + 'starts_with' => "LIKE '%s%%'", + ); + $where[] = 'n.title '. (isset($match_operators[$match]) ? $match_operators[$match] : $match_operators['contains']); + $args[] = $string; + } + elseif ($ids) { + $where[] = 'n.nid IN (' . db_placeholders($ids) . ')'; + $args = array_merge($args, $ids); + } + + $where_clause = $where ? 'WHERE ('. implode(') AND (', $where) .')' : ''; + $sql = db_rewrite_sql("SELECT n.nid, n.title AS node_title, n.type AS node_type FROM {node} n $where_clause ORDER BY n.title, n.type"); + $result = $limit ? db_query_range($sql, $args, 0, $limit) : db_query($sql, $args); + $references = array(); + while ($node = db_fetch_object($result)) { + $references[$node->nid] = array( + 'title' => $node->node_title, + 'rendered' => $node->node_title, + ); + } + + return $references; +} + +/** + * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users + */ +function node_reference_autocomplete($field_name, $string = '') { + $fields = content_fields(); + $field = $fields[$field_name]; + $match = isset($field['widget']['autocomplete_match']) ? $field['widget']['autocomplete_match'] : 'contains'; + $matches = array(); + + $references = _node_reference_potential_references($field, $string, $match, array(), 10); + foreach ($references as $id => $row) { + // Add a class wrapper for a few required CSS overrides. + $matches[$row['title'] ." [nid:$id]"] = '
'. $row['rendered'] . '
'; + } + drupal_json($matches); +} + +/** + * Implementation of hook_node_types. + */ +function node_reference_node_type($op, $info) { + switch ($op) { + case 'update': + // Reflect type name changes to the 'referenceable types' settings. + if (!empty($info->old_type) && $info->old_type != $info->type) { + $fields = field_info_fields(); + foreach ($fields as $field_name => $field) { + if ($field['type'] == 'node_reference' && isset($field['referenceable_types'][$info->old_type])) { + $field['referenceable_types'][$info->type] = empty($field['referenceable_types'][$info->old_type]) ? 0 : $info->type; + unset($field['referenceable_types'][$info->old_type]); + content_field_instance_update($field); + } + } + } + break; + } +} + +/** + * Theme preprocess function. + * + * Allows specific node templates for nodes displayed as values of a + * node_reference field with the 'full node' / 'teaser' formatters. + */ +function node_reference_preprocess_node(&$vars) { + // The 'referencing_field' attribute of the node is added by the 'teaser' + // and 'full node' formatters. + if (!empty($vars['node']->referencing_field)) { + $node = $vars['node']; + $field = $node->referencing_field; + $vars['template_files'][] = 'node-node_reference'; + $vars['template_files'][] = 'node-node_reference-'. $field['field_name']; + $vars['template_files'][] = 'node-node_reference-'. $node->type; + $vars['template_files'][] = 'node-node_reference-'. $field['field_name'] .'-'. $node->type; + } +} + +/** + * 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_node_reference_select($element) { + return $element['#children']; +} + +function theme_node_reference_buttons($element) { + return $element['#children']; +} + +function theme_node_reference_autocomplete($element) { + return $element['#children']; +} \ No newline at end of file === added directory 'modules/field/modules/number' === added file 'modules/field/modules/number/number.info' --- modules/field/modules/number/number.info 1970-01-01 00:00:00 +0000 +++ modules/field/modules/number/number.info 2009-01-15 16:50:15 +0000 @@ -0,0 +1,8 @@ +; $Id$ +name = Number +description = Defines numeric field types. +dependencies[] = field +package = Core - fields +core = 7.x + +files[]=number.module === added file 'modules/field/modules/number/number.module' --- modules/field/modules/number/number.module 1970-01-01 00:00:00 +0000 +++ modules/field/modules/number/number.module 2009-01-15 16:50:16 +0000 @@ -0,0 +1,424 @@ + array('arguments' => array('element' => NULL)), + 'field_formatter_number_integer' => array('arguments' => array('element' => NULL), 'function' => 'theme_field_formatter_number'), + 'field_formatter_number_decimal' => array('arguments' => array('element' => NULL), 'function' => 'theme_field_formatter_number'), + 'field_formatter_number_unformatted' => array('arguments' => array('element' => NULL)), + ); +} + +/** + * Implementation of hook_field_info(). + */ +function number_field_info() { + return array( + 'number_integer' => array( + 'label' => t('Integer'), + 'description' => t('This field stores a number in the database as an integer.'), + 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''), + 'default_widget' => 'number', + 'default_formatter' => 'number_integer', + ), + 'number_decimal' => array( + 'label' => t('Decimal'), + 'description' => t('This field stores a number in the database in a fixed decimal format.'), + 'settings' => array('precision' => 10, 'scale' => 2, 'decimal' => '.'), + 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''), + 'default_widget' => 'number', + 'default_formatter' => 'number_integer', + ), + 'number_float' => array( + 'label' => t('Float'), + 'description' => t('This field stores a number in the database in a floating point format.'), + 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''), + 'default_widget' => 'number', + 'default_formatter' => 'number_integer', + ), + ); +} + +function number_field_columns($field) { + switch ($field['type']) { + case 'number_integer' : + $colums = array( + 'value' => array( + 'type' => 'int', + 'not null' => FALSE + ), + ); + break; + + case 'number_float' : + $colums = array( + 'value' => array( + 'type' => 'float', + 'not null' => FALSE + ), + ); + break; + + case 'number_decimal' : + $colums = array( + 'value' => array( + 'type' => 'numeric', + 'precision' => $field['settings']['precision'], + 'scale' => $field['settings']['scale'], + 'not null' => FALSE + ), + ); + break; + } + return $colums; +} + +/** + * Implementation of hook_field_validate(). + */ +function number_field_validate($obj_type, $node, $field, $instance, &$items, $form) { + if (is_array($items)) { + foreach ($items as $delta => $item) { + $error_element = isset($item['_error_element']) ? $item['_error_element'] : ''; + if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']); + if ($item['value'] != '') { + if (is_numeric($instance['settings']['min']) && $item['value'] < $instance['settings']['min']) { + form_set_error($error_element, t('%name: the value may be no smaller than %min.', array('%name' => t($instance['label']), '%min' => $instance['settings']['min']))); + } + if (is_numeric($instance['settings']['max']) && $item['value'] > $instance['settings']['max']) { + form_set_error($error_element, t('%name: the value may be no larger than %max.', array('%name' => t($instance['label']), '%max' => $instance['settings']['max']))); + } + } + } + } +} + +/** + * Implementation of hook_content_is_empty(). + */ +function number_field_is_empty($item, $field) { + if (empty($item['value']) && (string)$item['value'] !== '0') { + return TRUE; + } + return FALSE; +} + +/** + * Implementation of hook_field_formatter_info(). + */ +function number_field_formatter_info() { + return array( + 'number_integer' => array( + 'label' => t('default'), + 'field types' => array('number_integer'), + 'settings' => array( + 'thousand_separator' => ' ', + 'decimal_separator' => '.', + 'scale' => 0, + 'prefix_suffix' => TRUE, + ), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'number_decimal' => array( + 'label' => t('default'), + 'field types' => array('number_decimal', 'number_float'), + 'settings' => array( + 'thousand_separator' => ' ', + 'decimal_separator' => '.', + 'scale' => 2, + 'prefix_suffix' => TRUE, + ), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'number_unformatted' => array( + 'label' => t('unformatted'), + 'field types' => array('number_integer', 'number_decimal', 'number_float'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + ); +} + +/** + * Theme function for 'unformatted' number field formatter. + */ +function theme_field_formatter_number_unformatted($element) { + return $element['#item']['value']; +} + +/** + * Proxy theme function for number field formatters. + */ +function theme_field_formatter_number($element) { + $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#field_name'], $element['#bundle']); + $value = $element['#item']['value']; + $settings = $element['#settings']; + $formatter_type = $element['#formatter']; + + if (empty($value) && $value !== '0') { + return ''; + } + + $output = number_format($value, $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']); + + if ($settings['prefix_suffix']) { + $prefixes = isset($instance['settings']['prefix']) ? explode('|', check_plain($instance['settings']['prefix'])) : array(0 => ''); + $suffixes = isset($instance['settings']['suffix']) ? explode('|', check_plain($instance['settings']['suffix'])) : array(0 => ''); + $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]; + $output = $prefix . $output . $suffix; + } + + return $output; +} + +/** + * Implementation of hook_field_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_field_widget_info() { + return array( + 'number' => array( + 'label' => t('Text field'), + 'field types' => array('number_integer', 'number_decimal', 'number_float'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_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_field_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 $instance + * the field instance 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_field_widget(&$form, &$form_state, $instance, $items, $delta = 0) { + $element = array( + '#type' => $instance['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 $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function number_process($element, $edit, $form_state, $form) { + $field_name = $element['#field_name']; + $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#field_name'], $element['#bundle']); + $field_key = $element['#columns'][0]; + + $value = isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : ''; + if ($field['type'] == 'number_decimal') { + $value = str_replace('.', $field['settings']['decimal'], $value); + } + + $element[$field_key] = array( + '#type' => 'textfield', + '#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' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] + 2 : 12, + '#maxlength' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] : 10, + '#attributes' => array('class' => 'number'), + // The following values were set by the content module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#description' => $element['#description'], + '#required' => $element['#required'], + '#field_name' => $element['#field_name'], + '#bundle' => $element['#bundle'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + + if (!empty($instance['settings']['prefix'])) { + $prefixes = explode('|', $instance['settings']['prefix']); + $element[$field_key]['#field_prefix'] = array_pop($prefixes); + } + if (!empty($instance['settings']['suffix'])) { + $suffixes = explode('|', $instance['settings']['suffix']); + $element[$field_key]['#field_suffix'] = array_pop($suffixes); + } + + // Make sure we don't wipe out element validation added elsewhere. + if (empty($element['#element_validate'])) { + $element['#element_validate'] = array(); + } + switch ($field['type']) { + case 'number_float': + $element['#element_validate'][] = 'number_float_validate'; + break; + case 'number_integer': + $element['#element_validate'][] = 'number_integer_validate'; + break; + case 'number_decimal': + $element['#element_validate'][] = 'number_decimal_validate'; + break; + } + + // Used so that hook_field('validate') knows where to flag an error. + $element['_error_element'] = array( + '#type' => 'value', + '#value' => implode('][', array_merge($element['#parents'], array($field_key))), + ); + + return $element; +} + +/** + * FAPI validation of an individual float element. + */ +function number_float_validate($element, &$form_state) { + $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#field_name'], $element['#bundle']); + $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) { + $error_field = implode('][', $element['#parents']) .']['. $field_key; + form_set_error($error_field, t('Only numbers and decimals are allowed in %field.', array('%field' => t($instance['label'])))); + } + else { + form_set_value($element[$field_key], $value, $form_state); + } + } +} + +/** + * FAPI validation of an individual integer element. + */ +function number_integer_validate($element, &$form_state) { + $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#field_name'], $element['#bundle']); + $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) { + $error_field = implode('][', $element['#parents']) .']['. $field_key; + form_set_error($error_field, t('Only numbers are allowed in %field.', array('%field' => t($instance['label'])))); + } + else { + form_set_value($element[$field_key], $value, $form_state); + } + } +} + +/** + * FAPI validation of an individual decimal element. + */ +function number_decimal_validate($element, &$form_state) { + $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#field_name'], $element['#bundle']); + $field_key = $element['#columns'][0]; + $value = $element['#value'][$field_key]; + + if (($element[$field_key]['#required'] || !empty($value))) { + $start = $value; + $value = preg_replace('@[^-0-9\\'. $field['settings']['decimal'] .']@', '', $value); + if ($start != $value) { + $error_field = implode('][', $element['#parents']) .']['. $field_key; + form_set_error($error_field, t('Only numbers and the decimal character (%decimal) are allowed in %field.', array('%decimal' => $field['settings']['decimal'], '%field' => t($instance['label'])))); + } + else { + $value = str_replace($field['settings']['decimal'], '.', $value); + $value = round($value, $field['settings']['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 === added directory 'modules/field/modules/option_widgets' === added file 'modules/field/modules/option_widgets/option_widgets.info' --- modules/field/modules/option_widgets/option_widgets.info 1970-01-01 00:00:00 +0000 +++ modules/field/modules/option_widgets/option_widgets.info 2009-01-15 16:50:16 +0000 @@ -0,0 +1,7 @@ +; $Id$ +name = Optionwidgets +description = Defines selection, check box and radio button widgets for text and numeric fields. +dependencies[] = field +package = Core - fields +core = 7.x +files[]=option_widgets.module === added file 'modules/field/modules/option_widgets/option_widgets.module' --- modules/field/modules/option_widgets/option_widgets.module 1970-01-01 00:00:00 +0000 +++ modules/field/modules/option_widgets/option_widgets.module 2009-01-15 16:50:15 +0000 @@ -0,0 +1,438 @@ +'. t('Create a list of options as a list in Allowed values list or as an array in PHP code. These values will be the same for %field in all field types.', array('%field' => $label)) .'

'; + + if ($widget_type == 'option_widgets_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 ($widget_type == 'option_widgets_buttons') { + $output .= '

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

'; + } + + if (in_array($field_type, array('text', 'number_integer', 'number_float', 'number_decimal')) + && in_array($widget_type, array('option_widgets_onoff', 'option_widgets_buttons', 'option_widgets_select'))) { + $form['field']['allowed_values_fieldset']['#collapsed'] = FALSE; + $form['field']['allowed_values_fieldset']['#description'] = $output; + + // If no 'allowed values' were set yet, add a remainder in the messages area. + if (empty($form_state['post']) + && empty($form['field']['allowed_values_fieldset']['allowed_values']['#default_value']) + && empty($form['field']['allowed_values_fieldset']['advanced_options']['allowed_values_php']['#default_value'])) { + drupal_set_message(t("You need to specify the 'allowed values' for this field."), 'warning'); + } + } + } +} + +/** + * Implementation of hook_theme(). + */ +function option_widgets_theme() { + return array( + 'option_widgets_select' => array( + 'arguments' => array('element' => NULL), + ), + 'option_widgets_buttons' => array( + 'arguments' => array('element' => NULL), + ), + 'option_widgets_onoff' => array( + 'arguments' => array('element' => NULL), + ), + 'option_widgets_none' => array( + 'arguments' => array('widget_type' => NULL, 'field_name' => NULL, 'node_type' => NULL), + ), + ); +} + +/** + * Implementation of hook_field_widget_info(). + * + * We need custom handling of multiple values because we need + * to combine them into a options list rather than display + * cardinality elements. We will use the field 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 option_widgets_field_widget_info() { + + return array( + 'option_widgets_select' => array( + 'label' => t('Select list'), + 'field types' => array('list', 'list_boolean', 'list_text', 'list_number'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'option_widgets_buttons' => array( + 'label' => t('Check boxes/radio buttons'), + 'field types' => array('list', 'list_boolean', 'list_text', 'list_number'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'option_widgets_onoff' => array( + 'label' => t('Single on/off checkbox'), + 'field types' => array('list_boolean'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_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 option_widgets_elements() { + return array( + 'option_widgets_select' => array( + '#input' => TRUE, + '#columns' => array('value'), '#delta' => 0, + '#process' => array('option_widgets_select_process'), + ), + 'option_widgets_buttons' => array( + '#input' => TRUE, + '#columns' => array('value'), '#delta' => 0, + '#process' => array('option_widgets_buttons_process'), + ), + 'option_widgets_onoff' => array( + '#input' => TRUE, + '#columns' => array('value'), '#delta' => 0, + '#process' => array('option_widgets_onoff_process'), + ), + ); +} + +/** + * Implementation of hook_field_widget(). + */ +function option_widgets_field_widget(&$form, &$form_state, $instance, $items, $delta = NULL) { + $element = array( + '#type' => $instance['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 $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function option_widgets_buttons_process($element, $edit, &$form_state, $form) { + $field = $form['#fields'][$element['#field_name']]['field']; + $instance = $form['#fields'][$element['#field_name']]['instance']; + $field_key = $element['#columns'][0]; + + // See if this element is in the database format or the transformed format, + // and transform it if necessary. + if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) { + $element['#value'] = option_widgets_data2form($element, $element['#default_value'], $field); + } + $options = option_widgets_options($field, $instance); + $multiple = isset($element['#multiple']) ? $element['#multiple'] : $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; + $element[$field_key] = array( + '#type' => $multiple ? 'checkboxes' : 'radios', + '#title' => $element['#title'], + '#description' => $element['#description'], + '#required' => isset($element['#required']) ? $element['#required'] : $instance['required'], + '#multiple' => $multiple, + '#options' => $options, + '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL, + ); + + // Set #element_validate in a way that it will not wipe out other + // validation functions already set by other modules. + if (empty($element['#element_validate'])) { + $element['#element_validate'] = array(); + } + array_unshift($element['#element_validate'], 'option_widgets_validate'); + + // Make sure field info will be available to the validator which + // does not get the values in $form. + $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']]; + 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 $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function option_widgets_select_process($element, $edit, &$form_state, $form) { + $field = $form['#fields'][$element['#field_name']]['field']; + $instance = $form['#fields'][$element['#field_name']]['instance']; + $field_key = $element['#columns'][0]; + + // See if this element is in the database format or the transformed format, + // and transform it if necessary. + if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) { + $element['#value'] = option_widgets_data2form($element, $element['#default_value'], $field); + } + + $options = option_widgets_options($field, $instance); + $element[$field_key] = array( + '#type' => 'select', + '#title' => $element['#title'], + '#description' => $element['#description'], + '#required' => isset($element['#required']) ? $element['#required'] : $instance['required'], + '#multiple' => isset($element['#multiple']) ? $element['#multiple'] : $field['cardinality'], + '#options' => $options, + '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL, + ); + + // Set #element_validate in a way that it will not wipe out other + // validation functions already set by other modules. + if (empty($element['#element_validate'])) { + $element['#element_validate'] = array(); + } + array_unshift($element['#element_validate'], 'option_widgets_validate'); + + // Make sure field info will be available to the validator which + // does not get the values in $form. + $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']]; + 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 option_widgets_onoff_process($element, $edit, &$form_state, $form) { + $field = $form['#fields'][$element['#field_name']]['field']; + $instance = $form['#fields'][$element['#field_name']]['instance']; + $field_key = $element['#columns'][0]; + + // See if this element is in the database format or the transformed format, + // and transform it if necessary. + if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) { + $element['#value'] = option_widgets_data2form($element, $element['#default_value'], $field); + } + $options = option_widgets_options($field, $instance); + $keys = array_keys($options); + $on_value = (!empty($keys) && isset($keys[1])) ? $keys[1] : NULL; + $element[$field_key] = array( + '#type' => 'checkbox', + '#title' => isset($options[$on_value]) ? $options[$on_value] : '', + '#description' => $element['#description'], + '#default_value' => isset($element['#value'][$field_key][0]) ? $element['#value'][$field_key][0] == $on_value : FALSE, + '#return_value' => $on_value, + ); + + // Set #element_validate in a way that it will not wipe out other + // validation functions already set by other modules. + if (empty($element['#element_validate'])) { + $element['#element_validate'] = array(); + } + array_unshift($element['#element_validate'], 'option_widgets_validate'); + + // Make sure field info will be available to the validator which + // does not get the values in $form. + $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']]; + return $element; +} + +/** + * FAPI function to validate option_widgets element. + */ +function option_widgets_validate($element, &$form_state) { + // Transpose selections from field => delta to delta => field, + // turning cardinality selected options into cardinality parent elements. + // Immediate parent is the delta, need to get back to parent's parent + // to create cardinality elements. + $field = $form_state['#fields'][$element['#field_name']]['field']; + $items = option_widgets_form2data($element, $field); + form_set_value($element, $items, $form_state); + + // Check we don't exceed the allowed number of values. + if ($field['cardinality'] >= 2) { + // Filter out 'none' value (if present, will always be in key 0) + $field_key = $element['#columns'][0]; + if (isset($items[0][$field_key]) && $items[0][$field_key] === '') { + unset($items[0]); + } + if (count($items) > $field['cardinality']) { + $field_key = $element['#columns'][0]; + form_error($element[$field_key], t('%name: this field cannot hold more that @count values.', array('%name' => t($field['widget']['label']), '@count' => $field['cardinality']))); + } + } +} + +/** + * 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 option_widgets_data2form($element, $items, $field) { + $field_key = $element['#columns'][0]; + $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#field_name'], $element['#bundle']); + $options = option_widgets_options($field, $instance); + + $items_transposed = field_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['cardinality'] || $element['#type'] == 'option_widgets_onoff') { + return array($field_key => $keys); + } + else { + return !empty($keys) ? array($field_key => $value) : array(); + } +} + +/** + * 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 option_widgets_form2data($element, $field) { + $field_key = $element['#columns'][0]; + $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#field_name'], $element['#bundle']); + $items = (array) $element[$field_key]['#value']; + $options = option_widgets_options($field, $instance); + + $values = array_values($items); + + if ($element['#type'] == 'option_widgets_onoff' && ($values[0] === 0)) { + $keys = array_keys($options); + $values = array(array_key_exists(0, $keys) ? $keys[0] : NULL); + } + + if (empty($values)) { + $values[] = NULL; + } + $result = field_transpose_array_rows_cols(array($field_key => $values)); + return $result; +} + +/** + * Helper function for finding the allowed values list for a field. + * + * See if there is a module hook for the option values. + * Otherwise, try list_allowed_values() for an options list. + */ +function option_widgets_options($field, $instance) { + $function = $field['module'] .'_allowed_values'; + $options = function_exists($function) ? $function($field) : (array) list_allowed_values($field); + // Add an empty choice for : + // - non required radios + // - non required selects + if (!$instance['required']) { + if ((in_array($instance['widget']['type'], array('option_widgets_buttons', 'node_reference_buttons', 'user_reference_buttons')) && !$field['cardinality']) + || (in_array($instance['widget']['type'], array('option_widgets_select', 'node_reference_select', 'user_reference_select')))) { + $options = array('' => theme('option_widgets_none', $instance)) + $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_option_widgets_none($instance) { + switch ($instance['widget']['type']) { + case 'option_widgets_buttons': + case 'node_reference_buttons': + case 'user_reference_buttons': + return t('N/A'); + case 'option_widgets_select': + case 'node_reference_select': + case 'user_reference_select': + return t('- None -'); + default : + return ''; + } +} + +/** + * FAPI themes for option_widgets. + * + * 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_option_widgets_select($element) { + return $element['#children']; +} + +function theme_option_widgets_onoff($element) { + return $element['#children']; +} + +function theme_option_widgets_buttons($element) { + return $element['#children']; +} \ No newline at end of file === added directory 'modules/field/modules/text' === added file 'modules/field/modules/text/text.info' --- modules/field/modules/text/text.info 1970-01-01 00:00:00 +0000 +++ modules/field/modules/text/text.info 2009-01-15 16:50:15 +0000 @@ -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[] = field +package = Core - fields +core = 7.x + +files[]=text.module === added file 'modules/field/modules/text/text.module' --- modules/field/modules/text/text.module 1970-01-01 00:00:00 +0000 +++ modules/field/modules/text/text.module 2009-01-15 16:50:16 +0000 @@ -0,0 +1,400 @@ + array( + 'arguments' => array('element' => NULL), + ), + 'text_textfield' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_text_default' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_text_plain' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_text_trimmed' => array( + 'arguments' => array('element' => NULL), + ), + ); +} + +/** + * Implementation of hook_field_info(). + */ +function text_field_info() { + return array( + 'text' => array( + 'label' => t('Text'), + 'description' => t('This field stores varchar text in the database.'), + 'settings' => array('max_length' => 255), + 'instance_settings' => array('text_processing' => 0), + 'default_widget' => 'text_textfield', + 'default_formatter' => 'text_default', + ), + // TODO : not a good name... + 'textarea' => array( + 'label' => t('Textarea'), + 'description' => t('This field stores long text in the database.'), + 'instance_settings' => array('text_processing' => 0), + 'default_widget' => 'text_textarea', + 'default_formatter' => 'text_default', + ), + ); +} + +/** + * Implementation of hook_field_schema(). + */ +function text_field_columns($field) { + if ($field['type'] == 'textarea') { + $columns = array( + 'value' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + ); + } + else { + $columns = array( + 'value' => array( + 'type' => 'varchar', + 'length' => $field['settings']['max_length'], + 'not null' => FALSE, + ), + ); + } + $columns += array( + 'format' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + ), + ); + return $columns; +} + +/** + * Implementation of hook_field_validate(). + */ +function text_field_validate($obj_type, $object, $field, $instance, $items, $form) { + if (is_array($items)) { + foreach ($items as $delta => $item) { + $error_element = isset($item['_error_element']) ? $item['_error_element'] : ''; + if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']); + if (!empty($item['value'])) { + if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) { + form_set_error($error_element, t('%name: the value may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length']))); + } + } + } + } +} + +function text_field_sanitize($obj_type, $object, $field, $instance, &$items) { + global $language; + foreach ($items as $delta => $item) { + // TODO D7 : this code is really node-related. + if (!empty($field['text_processing'])) { + $check = is_null($object) || (isset($object->build_mode) && $object->build_mode == NODE_BUILD_PREVIEW); + $text = isset($item['value']) ? check_markup($item['value'], $item['format'], isset($object->language) ? $object->language : $language, $check) : ''; + } + else { + $text = check_plain($item['value']); + } + $items[$delta]['safe'] = $text; + } +} + +/** + * Implementation of hook_field_is_empty(). + */ +function text_field_is_empty($item, $field) { + if (empty($item['value']) && (string)$item['value'] !== '0') { + return TRUE; + } + return FALSE; +} + +/** + * Implementation of hook_field_formatter_info(). + */ +function text_field_formatter_info() { + return array( + 'text_default' => array( + 'label' => t('Default'), + 'field types' => array('text', 'textarea'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'text_plain' => array( + 'label' => t('Plain text'), + 'field types' => array('text', 'textarea'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'text_trimmed' => array( + 'label' => t('Trimmed'), + 'field types' => array('text', 'textarea'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + ); +} + +/** + * Theme function for 'default' text field formatter. + */ +function theme_field_formatter_text_default($element) { + return $element['#item']['safe']; +} + +/** + * Theme function for 'plain' text field formatter. + */ +function theme_field_formatter_text_plain($element) { + return strip_tags($element['#item']['safe']); +} + +/** + * Theme function for 'trimmed' text field formatter. + */ +function theme_field_formatter_text_trimmed($element) { + $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#field_name'], $element['#bundle']); + return $field['settings']['text_processing'] ? $element['#item']['format'] : NULL; +} + +/** + * Implementation of hook_field_widget_info(). + * + * Here we indicate that the field 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_field_widget_info() { + return array( + 'text_textfield' => array( + 'label' => t('Text field'), + 'field types' => array('text'), + 'settings' => array('size' => 60), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'text_textarea' => array( + 'label' => t('Text area (multiple rows)'), + 'field types' => array('textarea'), + 'settings' => array('rows' => 5), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_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_field_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_field_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_field_widget so it can be + * plugged into any module that can provide it with valid + * $field information. + * + * field 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 field 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 $instance + * the field instance 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_field_widget(&$form, &$form_state, $instance, $items, $delta = 0) { + $element = array( + '#type' => $instance['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 $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + * + * TODO: For widgets to be actual FAPI 'elements', reusable outside of a + * 'field' context, they shoudn't rely on $field and $instance. The bits of + * information needed to adjust the behavior of the 'element' should be + * extracted in hook_field_widget() above. + */ +function text_textfield_process($element, $edit, $form_state, $form) { + $field = $form['#fields'][$element['#field_name']]['field']; + $instance = $form['#fields'][$element['#field_name']]['instance']; + $field_key = $element['#columns'][0]; + $delta = $element['#delta']; + + $element[$field_key] = array( + '#type' => 'textfield', + '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL, + '#autocomplete_path' => $element['#autocomplete_path'], + '#size' => $instance['widget']['settings']['size'], + '#attributes' => array('class' => 'text'), + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#description' => $element['#description'], + '#required' => $element['#required'], + '#field_name' => $element['#field_name'], + '#bundle' => $element['#bundle'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + + $element[$field_key]['#maxlength'] = !empty($field['settings']['max_length']) ? $field['settings']['max_length'] : NULL; + + if (!empty($instance['settings']['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); + } + + // Used so that hook_field('validate') knows where to flag an error. + // TODO: rework that. See http://groups.drupal.org/node/18019. + $element['_error_element'] = array( + '#type' => 'value', + '#value' => implode('][', array_merge($element['#parents'], array($field_key))), + ); + + 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 $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function text_textarea_process($element, $edit, $form_state, $form) { + $field = $form['#fields'][$element['#field_name']]['field']; + $instance = $form['#fields'][$element['#field_name']]['instance']; + $field_key = $element['#columns'][0]; + $delta = $element['#delta']; + $element[$field_key] = array( + '#type' => 'textarea', + '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL, + '#rows' => $instance['widget']['settings']['rows'], + '#weight' => 0, + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#description' => $element['#description'], + '#required' => $element['#required'], + '#field_name' => $element['#field_name'], + '#bundle' => $element['#bundle'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + + if (!empty($field['settings']['text_processing'])) { + $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format'; + $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); + } + + // Used so that hook_field('validate') knows where to flag an error. + $element['_error_element'] = array( + '#type' => 'value', + '#value' => implode('][', array_merge($element['#parents'], array($field_key))), + ); + 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 === added file 'modules/field/modules/text/text.test' --- modules/field/modules/text/text.test 1970-01-01 00:00:00 +0000 +++ modules/field/modules/text/text.test 2009-01-15 16:50:16 +0000 @@ -0,0 +1,39 @@ + t('Text Field'), + 'description' => t("Test the creation of text fields."), + 'group' => t('Field') + ); + } + + function setUp() { + parent::setUp('field', 'text', 'field_test'); + } + + // Test widgets. + + /** + * Test textfield widget. + */ + function testTextfieldWidget() { + // Create a field + $field = $this->drupalCreateField('text'); + $this->instance = $this->drupalCreateFieldInstance($field['field_name'], 'text_textfield', 'text_default', FIELD_TEST_BUNDLE); + + } + + /** + * Test textarea widget. + */ + + // Test formatters. + /** + * + */ +} === added directory 'modules/field/modules/user_reference' === added file 'modules/field/modules/user_reference/user_reference.info' --- modules/field/modules/user_reference/user_reference.info 1970-01-01 00:00:00 +0000 +++ modules/field/modules/user_reference/user_reference.info 2009-01-15 16:50:16 +0000 @@ -0,0 +1,9 @@ +; $Id$ +name = User Reference +description = Defines a field type for referencing a user from a node. +dependencies[] = field +dependencies[] = text +dependencies[] = option_widgets +package = Core - fields +core = 7.x +files[]=user_reference.module \ No newline at end of file === added file 'modules/field/modules/user_reference/user_reference.module' --- modules/field/modules/user_reference/user_reference.module 1970-01-01 00:00:00 +0000 +++ modules/field/modules/user_reference/user_reference.module 2009-01-15 16:50:15 +0000 @@ -0,0 +1,761 @@ + 'user_reference autocomplete', + 'page callback' => 'user_reference_autocomplete', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK + ); + return $items; +} + +/** + * Implementation of hook_theme(). + */ +function user_reference_theme() { + return array( + 'user_reference_select' => array( + 'arguments' => array('element' => NULL), + ), + 'user_reference_buttons' => array( + 'arguments' => array('element' => NULL), + ), + 'user_reference_autocomplete' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_formatter_user_reference_default' => array( + 'arguments' => array('element'), + ), + 'field_formatter_formatter_user_reference_plain' => array( + 'arguments' => array('element'), + ), + ); +} + +/** + * Implementation of hook_field_info(). + */ +function user_reference_field_info() { + return array( + 'user_reference' => array( + 'label' => t('User reference'), + 'description' => t('This field stores the ID of a related user as an integer value.'), + 'settings' => array('referenceable_roles', 'referenceable_status'), + 'default_widget' => 'user_reference_autocomplete', + 'default_formatter' => 'user_reference_default', + ), + ); +} + +/** + * Implementation of hook_field_settings(). + */ +function user_reference_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' => isset($field['referenceable_roles']) && 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' => isset($field['referenceable_status']) && is_array($field['referenceable_status']) ? array_filter($field['referenceable_status']) : array(1), + '#options' => array(1 => t('Active'), 0 => t('Blocked')), + ); + return $form; + + case 'database columns': + $columns = array( + 'uid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE), + ); + return $columns; + + } +} + +/** + * Implementation of hook_field(). + */ +function user_reference_field($op, $node, $field, &$items, $teaser, $page) { + switch ($op) { + case 'validate': + // Extract uids to check. + $ids = array(); + foreach ($items as $delta => $item) { + if (is_array($item) && !empty($item['uid']) && is_numeric($item['uid'])) { + $ids[] = $item['uid']; + } + } + $refs = _user_reference_potential_references($field, '', NULL, $ids); + foreach ($items as $delta => $item) { + if (is_array($item)) { + $error_element = isset($item['_error_element']) ? $item['_error_element'] : ''; + if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']); + if (!empty($item['uid']) && !isset($refs[$item['uid']])) { + form_set_error($error_element, t('%name: invalid user.', array('%name' => t($field['widget']['label'])))); + } + } + } + return $items; + } +} + +/** + * Implementation of hook_field_is_empty(). + */ +function user_reference_field_is_empty($item, $field) { + if (empty($item['uid'])) { + return TRUE; + } + return FALSE; +} + +/** + * Implementation of hook_field_formatter_info(). + */ +function user_reference_field_formatter_info() { + return array( + 'user_reference_default' => array( + 'label' => t('Default'), + 'field types' => array('user_reference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'user_reference_plain' => array( + 'label' => t('Plain text'), + 'field types' => array('user_reference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + ); +} + +/** + * Theme function for 'default' user_reference field formatter. + */ +function theme_field_formatter_formatter_user_reference_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' user_reference field formatter. + */ +function theme_field_formatter_formatter_user_reference_plain($element) { + $output = ''; + if (isset($element['#item']['uid']) && $account = user_load(array('uid' => $element['#item']['uid']))) { + $output = $account->name; + } + return $output; +} + +/** + * Implementation of hook_field_widget_info(). + * + * We need custom handling of multiple values for the user_reference_select + * widget because we need to combine them into a options list rather + * than display multiple elements. + * + * We will use the field 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 user_reference_field_widget_info() { + return array( + 'user_reference_select' => array( + 'label' => t('Select list'), + 'field types' => array('user_reference'), + 'settings' => array('autocomplete_match', 'reverse_link'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'user_reference_buttons' => array( + 'label' => t('Check boxes/radio buttons'), + 'field types' => array('user_reference'), + 'settings' => array('autocomplete_match', 'reverse_link'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'user_reference_autocomplete' => array( + 'label' => t('Autocomplete text field'), + 'field types' => array('user_reference'), + 'settings' => array('autocomplete_match', 'reverse_link'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_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 user_reference). + */ +function user_reference_elements() { + return array( + 'user_reference_select' => array( + '#input' => TRUE, + '#columns' => array('uid'), '#delta' => 0, + '#process' => array('user_reference_select_process'), + ), + 'user_reference_buttons' => array( + '#input' => TRUE, + '#columns' => array('uid'), '#delta' => 0, + '#process' => array('user_reference_buttons_process'), + ), + 'user_reference_autocomplete' => array( + '#input' => TRUE, + '#columns' => array('name'), '#delta' => 0, + '#process' => array('user_reference_autocomplete_process'), + '#autocomplete_path' => FALSE, + ), + ); +} + +/** + * Implementation of hook_field_widget_settings(). + */ +function user_reference_field_widget_settings($op, $widget) { + switch ($op) { + case 'form': + $form = array(); + $match = isset($widget['autocomplete_match']) ? $widget['autocomplete_match'] : 'contains'; + if ($widget['type'] == 'user_reference_autocomplete') { + $form['autocomplete_match'] = array( + '#type' => 'select', + '#title' => t('Autocomplete matching'), + '#default_value' => $match, + '#options' => array( + 'starts_with' => t('Starts with'), + 'contains' => t('Contains'), + ), + '#description' => t('Select the method used to collect autocomplete suggestions. Note that Contains can cause performance issues on sites with thousands of users.'), + ); + } + else { + $form['autocomplete_match'] = array('#type' => 'hidden', '#value' => $match); + } + $form['reverse_link'] = array( + '#type' => 'checkbox', + '#title' => t('Reverse link'), + '#default_value' => isset($widget['reverse_link']) ? $widget['reverse_link'] : 0, + '#description' => t('If selected, a reverse link back to the referencing node will displayed on the referenced user record.'), + ); + return $form; + } +} + +/** + * Implementation of hook_field_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. + * + * Field 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 field 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 $instance + * the field instance 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 user_reference_field_widget(&$form, &$form_state, $instance, $items, $delta = 0) { + switch ($instance['widget']['type']) { + case 'user_reference_select': + $element = array( + '#type' => 'user_reference_select', + '#default_value' => $items, + ); + break; + + case 'user_reference_buttons': + $element = array( + '#type' => 'user_reference_buttons', + '#default_value' => $items, + ); + break; + + case 'user_reference_autocomplete': + $element = array( + '#type' => 'user_reference_autocomplete', + '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, + '#value_callback' => 'user_reference_autocomplete_value', + ); + break; + } + return $element; +} + +/** + * Value for a user_reference autocomplete element. + * + * Substitute in the user name for the uid. + */ +function user_reference_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 $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function user_reference_select_process($element, $edit, $form_state, $form) { + // The user_reference_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'] : '', + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + '#field_name' => $element['#field_name'], + '#type_name' => $element['#type_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + if (empty($element[$field_key]['#element_validate'])) { + $element[$field_key]['#element_validate'] = array(); + } + array_unshift($element[$field_key]['#element_validate'], 'user_reference_optionwidgets_validate'); + 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 $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function user_reference_buttons_process($element, $edit, $form_state, $form) { + // The user_reference_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_buttons', + '#default_value' => isset($element['#value']) ? $element['#value'] : '', + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + '#field_name' => $element['#field_name'], + '#type_name' => $element['#type_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + if (empty($element[$field_key]['#element_validate'])) { + $element[$field_key]['#element_validate'] = array(); + } + array_unshift($element[$field_key]['#element_validate'], 'user_reference_optionwidgets_validate'); + 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 $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function user_reference_autocomplete_process($element, $edit, $form_state, $form) { + // The user_reference 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' => 'user_reference/autocomplete/'. $element['#field_name'], + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + '#field_name' => $element['#field_name'], + '#type_name' => $element['#type_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + if (empty($element[$field_key]['#element_validate'])) { + $element[$field_key]['#element_validate'] = array(); + } + array_unshift($element[$field_key]['#element_validate'], 'user_reference_autocomplete_validate'); + + // Used so that hook_field('validate') knows where to flag an error. + $element['_error_element'] = array( + '#type' => 'value', + // Wrapping the element around a text_textfield element creates a + // nested element, so the final id will look like 'field-name-0-uid-uid'. + '#value' => implode('][', array_merge($element['#parents'], array($field_key, $field_key))), + ); + return $element; +} + +/** + * Validate a select/buttons element. + * + * Remove the wrapper layer and set the right element's value. + * We don't know exactly where this element is, so we drill down + * through the element until we get to our key. + * + * We use $form_state['values'] instead of $element['#value'] + * to be sure we have the most accurate value when other modules + * like optionwidgets are using #element_validate to alter the value. + */ +function user_reference_optionwidgets_validate($element, &$form_state) { + $field_key = $element['#columns'][0]; + + $value = $form_state['values']; + $new_parents = array(); + foreach ($element['#parents'] as $parent) { + $value = $value[$parent]; + // Use === to be sure we get right results if parent is a zero (delta) value. + if ($parent === $field_key) { + $element['#parents'] = $new_parents; + form_set_value($element, $value, $form_state); + break; + } + $new_parents[] = $parent; + } +} + +/** + * Validate an autocomplete element. + * + * Remove the wrapper layer and set the right element's value. + * This will move the nested value at 'field-name-0-uid-uid' + * back to its original location, 'field-name-0-uid'. + */ +function user_reference_autocomplete_validate($element, &$form_state) { + $field_name = $element['#field_name']; + $type_name = $element['#type_name']; + $field = field_fields($field_name, $type_name); + $field_key = $element['#columns'][0]; + $value = $element['#value'][$field_key]; + $uid = NULL; + if (!empty($value)) { + $reference = _user_reference_potential_references($field, $value, 'equals', NULL, 1); + if (empty($reference)) { + form_error($element[$field_key], t('%name: found no valid user with that name.', array('%name' => t($field['widget']['label'])))); + } + else { + $uid = key($reference); + } + } + form_set_value($element, $uid, $form_state); +} + +/** + * Implementation of hook_allowed_values(). + */ +function user_reference_allowed_values($field) { + $references = _user_reference_potential_references($field); + + $options = array(); + foreach ($references as $key => $value) { + // Views theming runs check_plain (htmlentities) on the values. + // We reverse that with html_entity_decode. + $options[$key] = html_entity_decode(strip_tags($value['rendered']), ENT_QUOTES); + } + return $options; +} + +/** + * Fetch an array of all candidate referenced users. + * + * This info is used in various places (aloowed values, autocomplete results, + * input validation...). Some of them only need the uids, others nid + names, + * others yet uid + names + rendered row (for display in widgets). + * The array we return contains all the potentially needed information, and lets + * consumers use the parts they actually need. + * + * @param $field + * The field description. + * @param $string + * Optional string to filter usernames on (used by autocomplete) + * @param $match + * Operator to match filtered name against, can be any of: + * 'contains', 'equals', 'starts_with' + * @param $ids + * Optional user ids to lookup (the $string and $match arguments will be + * ignored). + * @param $limit + * If non-zero, limit the size of the result set. + * + * @return + * An array of valid users in the form: + * array( + * uid => array( + * 'title' => The user name, + * 'rendered' => The text to display in widgets (can be HTML) + * ), + * ... + * ) + */ +function _user_reference_potential_references($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { + static $results = array(); + + // Create unique id for static cache. + $cid = $field['field_name'] .':'. $match .':'. ($string !== '' ? $string : implode('-', $ids)) .':'. $limit; + if (!isset($results[$cid])) { + $references = FALSE; + if ($references === FALSE) { + $references = _user_reference_potential_references_standard($field, $string, $match, $ids, $limit); + } + + // Store the results. + $results[$cid] = $references; + } + + return $results[$cid]; +} + +/** + * Helper function for _user_reference_potential_references(): + * referenceable users defined by user role and status + */ +function _user_reference_potential_references_standard($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { + $where = array(); + $args = array(); + $join = array(); + + if ($string !== '') { + $match_operators = array( + 'contains' => "LIKE '%%%s%%'", + 'equals' => "= '%s'", + 'starts_with' => "LIKE '%s%%'", + ); + $where[] = 'u.name '. (isset($match_operators[$match]) ? $match_operators[$match] : $match_operators['contains']); + $args[] = $string; + } + elseif ($ids) { + $where[] = 'u.uid IN (' . db_placeholders($ids) . ')'; + $args = array_merge($args, $ids); + } + 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(); + $where_clause = $where ? 'WHERE ('. implode(') AND (', $where) .')' : ''; + $result = db_query('SELECT u.name, u.uid FROM {users} u '. implode(' ', $join) ." $where_clause ORDER BY u.name ASC", $args); + while ($user = db_fetch_object($result)) { + $users[$user->uid] = array( + 'title' => $user->name, + 'rendered' => $user->name, + ); + } + return $users; +} + +/** + * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users + */ +function user_reference_autocomplete($field_name, $string = '') { + $fields = field_info_fields(); + $field = $fields[$field_name]; + $match = isset($field['widget']['autocomplete_match']) ? $field['widget']['autocomplete_match'] : 'contains'; + $matches = array(); + + $references = _user_reference_potential_references($field, $string, $match, array(), 10); + foreach ($references as $id => $row) { + // Add a class wrapper for a few required CSS overrides. + $matches[$row['title']] = '
'. $row['rendered'] . '
'; + } + drupal_json($matches); +} + +/** + * Implementation of hook_user(). + */ +function user_reference_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 user_reference 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 field 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'] == 'user_reference' && !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->user_reference = $additions; + return; + break; + + case 'view': + if (!empty($account->user_reference)) { + $node_types = content_types(); + $additions = array(); + $values = array(); + foreach ($account->user_reference 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' => check_plain($node_types[$node_type]['name']), + '#value' => theme('item_list', $values[$node_type]), + ); + } + } + if ($additions) { + $account->content['user_reference'] = $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_user_reference_select($element) { + return $element['#children']; +} + +function theme_user_reference_buttons($element) { + return $element['#children']; +} + +function theme_user_reference_autocomplete($element) { + return $element['#children']; +} \ No newline at end of file === added file 'modules/field/sample_code.php' --- modules/field/sample_code.php 1970-01-01 00:00:00 +0000 +++ modules/field/sample_code.php 2009-01-15 16:50:16 +0000 @@ -0,0 +1,103 @@ + 'field_single', + 'type' => 'text', +); +field_create_field($field); + +$instance = array( + 'field_name' => 'field_single', + 'bundle' => 'article', + 'label' => 'Single', + 'widget' => array( + 'type' => 'text_textfield', + ), + 'display' => array( + 'full' => array( + 'label' => 'above', + 'type' => 'text_default', + 'exclude' => 0, + ), + ), +); +field_create_instance($instance); + +$instance['bundle'] = 'user'; +field_create_instance($instance); + + +$field = array( + 'field_name' => 'field_multiple', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'type' => 'text', +); +field_create_field($field); + +$instance = array( + 'field_name' => 'field_multiple', + 'bundle' => 'article', + 'label' => 'Multiple', + 'widget' => array( + 'type' => 'text_textfield', + ), + 'display' => array( + 'full' => array( + 'label' => 'above', + 'type' => 'text_default', + 'exclude' => 0, + ), + ), +); +field_create_instance($instance); + +$instance['bundle'] = 'user'; +field_create_instance($instance); + + +// Number +$field = array( + 'field_name' => 'field_integer', + 'type' => 'number_integer', +); +field_create_field($field); +$instance = array( + 'field_name' => 'field_integer', + 'bundle' => 'article', + 'label' => 'Integer', + 'widget' => array( + 'type' => 'number', + ), + 'display' => array( + 'full' => array( + 'label' => 'above', + 'type' => 'number_integer', + 'exclude' => 0, + ), + ), +); +field_create_instance($instance); + +$field = array( + 'field_name' => 'field_decimal', + 'type' => 'number_decimal', +); +field_create_field($field); +$instance = array( + 'field_name' => 'field_decimal', + 'bundle' => 'article', + 'label' => 'Decimal', + 'widget' => array( + 'type' => 'number', + ), + 'display' => array( + 'full' => array( + 'label' => 'above', + 'type' => 'number_decimal', + 'exclude' => 0, + ), + ), +); +field_create_instance($instance); + + === added directory 'modules/field/theme' === added file 'modules/field/theme/field.css' --- modules/field/theme/field.css 1970-01-01 00:00:00 +0000 +++ modules/field/theme/field.css 2009-01-15 16:50:16 +0000 @@ -0,0 +1,30 @@ +/* Node display */ +.field .field-label, +.field .field-label-inline, +.field .field-label-inline-first { + font-weight:bold; +} +.field .field-label-inline, +.field .field-label-inline-first { + display:inline; +} +.field .field-label-inline { + visibility:hidden; +} + +.node-form .field-multiple-table td.field-multiple-drag { + width:30px; + padding-right:0; +} +.node-form .field-multiple-table td.field-multiple-drag a.tabledrag-handle{ + padding-right:.5em; +} + +.node-form .field-add-more .form-submit{ + margin:0; +} + +.node-form .number { + display:inline; + width:auto; +} \ No newline at end of file === added file 'modules/field/theme/field.tpl.php' --- modules/field/theme/field.tpl.php 1970-01-01 00:00:00 +0000 +++ modules/field/theme/field.tpl.php 2009-01-15 16:50:16 +0000 @@ -0,0 +1,49 @@ + + +
+ +
+ +
+ $item) : + if (!$item['empty']) : ?> +
+ +
+
+ + +
+ +
+
+ === added directory 'modules/materialized_view' === added file 'modules/materialized_view/materialized_view.admin.inc' --- modules/materialized_view/materialized_view.admin.inc 1970-01-01 00:00:00 +0000 +++ modules/materialized_view/materialized_view.admin.inc 2009-01-15 16:50:15 +0000 @@ -0,0 +1,38 @@ + $view) { + $view_row = db_fetch_object(db_query('SELECT * FROM {dna_view} WHERE foreign_table = "%s"', $table)); + $rows[] = array( + $view['module'], + $table, + ($view_row ? db_result(db_query('SELECT COUNT(*) FROM {node} WHERE nid <= %d', $view_row->max_nid)) : t('Unknown')), + ($view_row ? $view_row->max_nid : t('Unknown')), + ); + } + + if (empty($rows)) { + $content[] = t('No implementations of hook_dna() found.'); + } + else { + $content[] = theme('table', $cols, $rows); + } + + return implode("\n", $content); +} \ No newline at end of file === added file 'modules/materialized_view/materialized_view.class.inc' --- modules/materialized_view/materialized_view.class.inc 1970-01-01 00:00:00 +0000 +++ modules/materialized_view/materialized_view.class.inc 2009-01-15 16:50:15 +0000 @@ -0,0 +1,353 @@ +name = $name; + } + + /** + * Gets the name of the materialized view. + * + * @returns + * The name of the materialized view. + */ + public function getName() { + return $this->name; + } + + /** + * Adds extra column that provides data consolidation but + * is not indexed. + * + * @param $column + * MVColumn object to be used as the data source. + */ + public function addExtraColumn(MVColumn $column) { + $this->extra_columns[] = $column; + } + + /** + * Adds filter with a predefined test that runs when objects are updated. + * + * @param $column + * MVColumn object to be used for sorting. + * @param $operation + * The operation to be used on this column. + * @param $value + * The value or values to be used for the operation. + */ + public function addStaticFilter(MVColumn $column, MVOperator $operator) { + $filter = new stdClass(); + $filter->column = $column; + $filter->operator = $operator; + $this->static_filters[] = $filter; + } + + /** + * Adds filter configured to run at query time. + * + * @param $column + * MVColumn object to be used for sorting. + * @param $operation + * The operation to be used on this column. + */ + public function addDynamicFilter(MVColumn $column, MVOperator $operator) { + $filter = new stdClass(); + $filter->column = $column; + $filter->operator = $operator; + $this->dynamic_filters[] = $filter; + } + + /** + * Adds sort column configured to run at query time. + * + * @param $column + * MVColumn object to be used for sorting. + * @param $direction + * Constant defining sort order. Use MV_SORT_ASCENDING + * or MV_SORT_DESCENDING. + */ + public function addSort(MVColumn $column, $direction) { + $sort = new stdClass(); + $sort->column = $column; + $sort->direction = $direction; + $this->sorts[] = $sort; + } + + /** + * Get an array listing the primary key columns. + */ + protected function getPrimaryKey() { + $columns = array('entity', 'entity_id'); + + // Add dynamic filter columns as components of the primary key + foreach ($this->dynamic_filters as $dynamic_filter) { + $columns[] = $dynamic_filter->column->getName(); + } + + return $columns; + } + + /** + * Get an array listing columns to be indexed. + */ + protected function getIndex() { + $index_columns = array(); + + // TODO: Order columns by =, LIKE, IN, and then the rest. + foreach ($this->dynamic_filters as $dynamic_filter) { + $index_columns[] = $dynamic_filter->column->getName(); + // TODO: Return the current index columns if we encounter + // a filter that ends the index. + } + + foreach ($this->sorts as $sort) { + // TODO: Add in sorts until direction changes + $index_columns[] = $sort->column->getName(); + } + + return $index_columns; + } + + /** + * Build the schema for the materialized view table. + */ + public function getSchema() { + $schema = array(); + + $schema['fields'] = array( + 'entity' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'unsigned' => TRUE, + 'description' => 'The type ID of the entity.', + ), + 'entity_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'unsigned' => TRUE, + 'description' => 'The ID of the entity this row belongs to.', + ), + ); + + // Add dynamic filter columns + foreach ($this->dynamic_filters as $dynamic_filter) { + $schema['fields'][$dynamic_filter->column->getName()] = $dynamic_filter->column->getSchema(); + } + + // Add sort columns + foreach ($this->sorts as $sort) { + $schema['fields'][$sort->column->getName()] = $sort->column->getSchema(); + } + + // Add extra columns + foreach ($this->extra_columns as $extra_column) { + $schema['fields'][$extra_column->getName()] = $extra_column->getSchema(); + } + + $schema['description'] = 'Materialized view storage for ' . $this->name . '.'; + $schema['indexes'][$this->name . '_mv'] = $this->getIndex(); + $schema['primary_key'] = $this->getPrimaryKey(); + + return $schema; + } + + /** + * Install the schema for this materialized view. + */ + public function installSchema() { + $ret = array(); + + $this->uninstallSchema(); + db_create_table($ret, $this->name, $this->getSchema()); + } + + /** + * Uninstall the schema for this materialized view. + */ + public function uninstallSchema() { + $ret = array(); + if (db_table_exists($this->name)) { + db_drop_table($ret, $this->name); + } + } + + /** + * Update an object in this materialized view or remove it + * if it no longer meets criteria for inclusion. + * + * @param $entity + * The entity type ID. + * @param $entity_id + * The entity ID. + */ + public function update($entity, $entity_id) { + $include = TRUE; + + // Start a transaction. + // TODO: Implement + + // Delete the existing rows related to the object in the database. + $this->delete($entity, $entity_id); + + // Loop through filters to determine if the updated object qualifies for inclusion. + foreach ($this->static_filters as $static_filter) { + + // Get the value of the column. + $value = $static_filter->column->getValue($entity, $entity_id); + + // Apply the operator + if (!$static_filter->operator->apply($value)) { + return; + } + } + + // Add the row(s) to the MV if the object meets static criteria. + if ($include) { + // We always need to write the entity and entity_id to the row. + $keys = $write = array( + 'entity' => $entity, + 'entity_id' => $entity_id, + ); + + // Get values for the dynamic filter columns + foreach ($this->dynamic_filters as $dynamic_filter) { + $write[$dynamic_filter->column->getName()] = $dynamic_filter->column->getValue($entity, $entity_id); + } + + // Get values for the sort columns + foreach ($this->sorts as $sort) { + $write[$sort->column->getName()] = $sort->column->getValue($entity, $entity_id); + } + + // Write the record to the database + db_insert($this->name) + ->fields(array_keys($write)) + ->values($write) + ->execute(); + + // TODO: Update this to do cartesian joins when getValue returns an array. + } + } + + /** + * Delete an object from this materialized view. + * + * @param $entity + * The entity type ID. + * @param $entity_id + * The entity ID. + */ + public function delete($entity, $entity_id) { + // Delete rows related to the object. + + watchdog('mv', 'Deleting: ' . $entity_id); + + $num_deleted = db_delete($this->name) + ->condition('entity', $entity) + ->condition('entity_id', $entity_id) + ->execute(); + + return $num_deleted; + } +} + +/** + * Defines an operator for use in static and dynamic filters. + */ +abstract class MVOperator { + /** + * Applies the operator to $value. + * + * @param $value + * The value to be compared. + * + * @returns boolean + */ + abstract public function apply($value); + + /** + * Determines whether the operator would end the + * usefulness of an index if added to the index. + * + * @returns boolean + */ + abstract public function endsIndex(); + + /** + * Determines whether the operator is indexable. + * + * @returns boolean + */ + abstract public function indexable(); +} + +/** + * Defines the equality operator for use in static and dynamic filters. + */ +class MVEqualityOperator extends MVOperator { + protected $comparison_value = NULL; + + public function __construct($value = NULL) { + $this->comparison_value = $value; + } + + public function apply($value) { + return $value == $this->comparison_value; + } + + public function endsIndex() { + return FALSE; + } + + public function indexable() { + return TRUE; + } +} + +/** + * Defines a column data source for use in materialized views. + */ +abstract class MVColumn { + /** + * Retreive the value to use for this column, given an entity + * and entity ID. + * + * @param $entity + * The entity type ID. + * @param $entity_id + * The entity ID. + */ + abstract public function getValue($entity, $entity_id); + + /** + * Retrieve a column scheme suitable for use in $table['fields']. + * + * @returns The column scheme. + */ + abstract public function getSchema(); + + /** + * Retrieve the column name. + * + * @returns A column name. + */ + abstract public function getName(); +} === added file 'modules/materialized_view/materialized_view.info' --- modules/materialized_view/materialized_view.info 1970-01-01 00:00:00 +0000 +++ modules/materialized_view/materialized_view.info 2009-01-15 16:50:15 +0000 @@ -0,0 +1,11 @@ +; $Id$ +name = Materialized View +description = Materializes defined views of fields for scalability. +package = Core +version = VERSION +core = 7.x +files[] = materialized_view.module +files[] = materialized_view.class.inc +files[] = materialized_view.admin.inc +files[] = materialized_view.sources.inc +dependencies[] = field_sql_storage === added file 'modules/materialized_view/materialized_view.install' --- modules/materialized_view/materialized_view.install 1970-01-01 00:00:00 +0000 +++ modules/materialized_view/materialized_view.install 2009-01-15 16:50:15 +0000 @@ -0,0 +1,81 @@ +uninstallSchema(); + } + + // Remove module tables. + drupal_uninstall_schema('materialized_view'); +} + +/** + * Implementation of hook_schema(). + */ +function materialized_view_schema() { + $schema['materialized_view'] = array( + 'description' => 'Stores information about materialized views.', + 'fields' => array( + 'mvid' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'description' => 'The primary identifier for a materialized view.', + ), + 'schema_hash' => array( + 'type' => 'char', + 'length' => 40, + 'not null' => TRUE, + 'description' => 'SHA1 hash of the serialized schema array.', + ), + ), + 'primary key' => array('mvid'), + ); + + $schema['materialized_view_indexing'] = array( + 'description' => 'Stores information about the status of materialized view indexing.', + 'fields' => array( + 'mvid' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'description' => 'The primary identifier for a materialized view.', + ), + 'entity' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => 32, + 'default' => '', + 'description' => 'The entity type being indexed.', + ), + 'max_indexed_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The maximum ID currently indexed for this content type and materialized view.', + ), + ), + 'primary key' => array('mvid', 'entity'), + ); + + return $schema; +} === added file 'modules/materialized_view/materialized_view.module' --- modules/materialized_view/materialized_view.module 1970-01-01 00:00:00 +0000 +++ modules/materialized_view/materialized_view.module 2009-01-15 16:50:16 +0000 @@ -0,0 +1,210 @@ +uninstallSchema(); + } + } + } + } +} + +/** + * Implementation of hook_exit(). + */ +function materialized_view_exit() { + + // Load the job queue. + $jobs = _materialized_view_queue(); + + // Load the materialized views. + $materialized_views = materialized_view_get(); + + // Loop through every combination of job and materialized view. + foreach ($jobs as $job) { + foreach ($materialized_views as $materialized_view) { + + // Run the update; otherwise, delete. + if ($job->update) { + $materialized_view->update($job->entity, $job->entity_id); + } + else { + $materialized_view->delete($job->entity, $job->entity_id); + } + } + } +} + +/** + * Allow queueing of updates so they occur after all saves are complete. + */ +function _materialized_view_queue($entity = NULL, $entity_id = NULL, $update = TRUE) { + static $jobs = array(); + + // If no entity is specified, return the contents of the queue. + if (!$entity) { + return $jobs; + } + + // Queue the new job. + $job = new stdClass(); + $job->entity = $entity; + $job->entity_id = $entity_id; + $job->update = $update; + $jobs[] = $job; +} + +/** + * Update all materialized views for the given object. + * + * @param $entity + * The entity type ID. + * @param $entity_id + * The entity ID. + */ +function materialized_view_update_all($entity, $entity_id) { + _materialized_view_queue($entity, $entity_id); +} + +/** + * Delete the given object from all materialized views. + * + * @param $entity + * The entity type ID. + * @param $entity_id + * The entity ID. + */ +function materialized_view_delete_all($entity, $entity_id) { + _materialized_view_queue($entity, $entity_id, FALSE); +} + +/** + * Check that all materialized views are registered for indexing + * and have up-to-date schema. + */ +function materialized_view_reconcile() { + // Get a list of all entity types. + // TODO: Implement; currently blocked by Field API + + // Load the list of materialized views. + $materialized_views = materialized_view_get(); + + foreach ($materialized_views as $materialized_view) { + // Load the mview record from the database. + // TODO: Use DB-TNG. + $mv_row = db_fetch_array(db_query('SELECT * FROM {materialized_view} WHERE mvid = "%s"', $materialized_view->getName())); + + // Check that any schema for this MV are installed. + if ($mv_row) { + // Check that schema are present and up-to-date. + $schema_hash = sha1(serialize($materialized_view->getSchema())); + if ($schema_hash != $mv_row->schema_hash) { + $materialized_view->uninstallSchema(); + $materialized_view->installSchema(); + + // Update the schema hash in the database. + db_update('materialized_view') + ->fields(array( + 'schema_hash' => $schema_hash, + )) + ->condition('mvid', $materialized_view->getName()) + ->execute(); + + // Reset all indexing by clearing all rows in the index tracker for this MV. + db_delete('materialized_view_index') + ->condition('mvid', $materialized_view->getName()) + ->execute(); + } + } + else { + // Install the schema for the first time. + $materialized_view->installSchema(); + + // Update schema has in the database + $schema_hash = sha1(serialize($materialized_view->getSchema())); + db_update('materialized_view') + ->fields(array( + 'schema_hash' => $schema_hash, + )) + ->condition('mvid', $materialized_view->getName()) + ->execute(); + } + + // Check that all entity types are registered + // for indexing against this view. + // TODO: Implement; currently blocked by Field API + } + + // Remove tables and indexing data for views that no longer have registration. + $res = db_query('SELECT mvid FROM {materialized_view}'); + while ($row = db_fetch_array($res)) { + if (!array_key_exists($row['mvid'], $materialized_views)) { + $ret = array(); + if (db_table_exists($row->mvid)) { + db_drop_table($ret, $row->mvid); + } + } + } +} + +/** + * Retrieve the object for a materialized view by name + * or retrieve an array of all materialized views. + * + * @param $name + * The name of the materialized view. + * Pass NULL to get all materialized views. + */ +function materialized_view_get($name = NULL, $reset = FALSE) { + static $materialized_views = NULL; + + // Check if the set of materialized views is in the static cache. + if (is_null($materialized_views) || $reset) { + $materialized_views = array(); + $hook_results = module_invoke_all('materialized_view_info'); + + // Loop through materialized views and load them into the cache, + // keyed by name. + foreach ($hook_results as $hook_result) { + $materialized_views[$hook_result->getName()] = $hook_result; + } + } + + // Return just one MV object if a name was specified. + if (!is_null($name)) { + return $materialized_views[$name]; + } + + return $materialized_views; +} + +/** + * Implementation of hook_cron(). + */ +function materialized_view_cron() { + // Use the materialized_view_indexing table to index objects. + $res = db_query('SELECT * FROM {materialized_view_index}'); + while ($row = db_fetch_array($res)) { + // TODO: Implement + } +} === added file 'modules/materialized_view/materialized_view.sources.inc' --- modules/materialized_view/materialized_view.sources.inc 1970-01-01 00:00:00 +0000 +++ modules/materialized_view/materialized_view.sources.inc 2009-01-15 16:50:16 +0000 @@ -0,0 +1,73 @@ +field = $field; + } + + public function getValue($entity, $entity_id) { + // TK + } + + public function getSchema() { + return _field_sql_storage_schema($field); + } + + public function getName() { + // TK + } +} + +/* + * For node.module: + */ + +class MVNodeTitleColumn extends MVColumn { + public function getValue($entity, $entity_id) { + $node = node_load($entity_id, array(), TRUE); + return $node->title; + } + + public function getSchema() { + $schema = drupal_get_schema_unprocessed('node', 'node'); + return $schema['fields']['title']; + } + + public function getName() { + return 'node_title'; + } +} + +class MVNodeStatusColumn extends MVColumn { + public function getValue($entity, $entity_id) { + $node = node_load($entity_id, array(), TRUE); + return $node->status; + } + + public function getSchema() { + $schema = drupal_get_schema_unprocessed('node', 'node'); + return $schema['fields']['status']; + } + + public function getName() { + return 'node_status'; + } +} + +function materialized_view_nodeapi_insert($node) { + materialized_view_update_all(1, $node->nid); +} + +function materialized_view_nodeapi_update($node) { + materialized_view_update_all(1, $node->nid); +} + +function materialized_view_nodeapi_delete($node) { + materialized_view_delete_all(1, $node->nid); +} === added file 'modules/materialized_view/materialized_view.test' --- modules/materialized_view/materialized_view.test 1970-01-01 00:00:00 +0000 +++ modules/materialized_view/materialized_view.test 2009-01-15 16:50:15 +0000 @@ -0,0 +1,20 @@ + t('Materialized View tests'), + 'description' => t('Test Materialized Views API.'), + 'group' => t('Field'), + ); + } + + function setUp() { + parent::setUp('field_sql_storage', 'materialized_view'); + } + + function testBasicStaticFilter() { + + } + +} === modified file 'modules/node/node.module' --- modules/node/node.module 2009-01-14 22:00:06 +0000 +++ modules/node/node.module 2009-01-15 16:50:53 +0000 @@ -152,6 +152,49 @@ } /** + * Implementation of hook_fieldable_info(). + */ +function node_fieldable_info() { + $return = array( + 'node' => array( + 'name' => t('Node'), + 'id key' => 'nid', + 'revision key' => 'vid', + 'bundle key' => 'type', + // Node.module handles its own caching. + // 'cacheable' => FALSE, + // Bundles must provide human readable name so + // we can create help and error messages about them. + 'bundles' => node_get_types('names'), + ), + ); + return $return; +} + + +/** + * Implementation of hook_field_build_modes(). + */ +function node_field_build_modes($obj_type) { + $modes = array(); + if ($obj_type == 'node') { + $modes = array( + 'teaser' => t('Teaser'), + 'full' => t('Full node'), + NODE_BUILD_RSS => t('RSS'), + NODE_BUILD_PRINT => t('Print'), + ); + if (module_exists('search')) { + $modes += array( + NODE_BUILD_SEARCH_INDEX => t('Search Index'), + NODE_BUILD_SEARCH_RESULT => t('Search Result'), + ); + } + } + return $modes; +} + +/** * Gather a listing of links to nodes. * * @param $result @@ -535,6 +578,10 @@ if ($is_existing) { db_update('node_type')->fields($fields)->condition('type', $existing_type)->execute(); + if (!empty($type->old_type) && $type->old_type != $type->type) { + drupal_function_exists('field_attach_rename_bundle'); + field_attach_rename_bundle($type->old_type, $type->type); + } module_invoke_all('node_type', 'update', $type); return SAVED_UPDATED; } @@ -542,6 +589,9 @@ $fields['orig_type'] = (string) $type->orig_type; db_insert('node_type')->fields($fields)->execute(); + drupal_function_exists('field_attach_create_bundle'); + field_attach_create_bundle($type->type); + module_invoke_all('node_type', 'insert', $type); return SAVED_NEW; } @@ -859,6 +909,10 @@ } } + // Attach fields. + drupal_function_exists('field_attach_load'); + field_attach_load('node', $queried_nodes, $vid ? FIELD_LOAD_REVISION : FIELD_LOAD_CURRENT); + // Call hook_nodeapi_load(), pass the node types so modules can return early // if not acting on types in the array. foreach (module_implements('nodeapi_load') as $module) { @@ -939,6 +993,9 @@ } } + // Validate fields + module_invoke('field', 'attach_validate', 'node', $node, $form); + // Do node-type-specific validation checks. node_invoke($node, 'validate', $form); node_invoke_nodeapi($node, 'validate', $form); @@ -953,6 +1010,9 @@ // Convert the node to an object, if necessary. $node = (object)$node; + drupal_function_exists('field_attach_presave'); + field_attach_presave('node', $node); + // Generate the teaser, but only if it hasn't been set (e.g. by a // module-provided 'teaser' form item). if (!isset($node->teaser)) { @@ -1065,6 +1125,12 @@ // node_invoke($node, 'insert') or // node_invoke($node, 'update'). node_invoke($node, $op); + + // Save fields. + $function = "field_attach_$op"; + drupal_function_exists($function); + $function('node', $node); + node_invoke_nodeapi($node, $op); // Update the node access table for this node. @@ -1198,9 +1264,13 @@ $node = node_prepare($node, $teaser); } + // Build fields content. + drupal_function_exists('field_attach_view'); + $node->content += field_attach_view('node', $node, $teaser); + // Allow modules to make their own additions to the node. node_invoke_nodeapi($node, 'view', $teaser); - + // Allow modules to modify the structured node. drupal_alter('node_view', $node, $teaser); @@ -2383,10 +2453,10 @@ if (count($or->conditions())) { $query->condition($or); } - + $query->condition("{$access_alias}.grant_$op", 1, '>='); } - } + } } /** @@ -3018,7 +3088,7 @@ */ function node_elements() { $type['node_links'] = array(); - + return $type; } === modified file 'modules/node/node.pages.inc' --- modules/node/node.pages.inc 2008-12-30 17:00:08 +0000 +++ modules/node/node.pages.inc 2009-01-15 16:50:16 +0000 @@ -257,7 +257,11 @@ ); } $form['#validate'][] = 'node_form_validate'; - $form['#theme'] = array($node->type . '_node_form', 'node_form'); + $form['#theme'] = array($node->type . '_node_form', 'node_form'); + + drupal_function_exists('field_attach_form'); + field_attach_form('node', $node, $form, $form_state); + return $form; } === modified file 'modules/node/node.tpl.php' --- modules/node/node.tpl.php 2008-12-31 13:00:08 +0000 +++ modules/node/node.tpl.php 2009-01-15 16:50:16 +0000 @@ -20,6 +20,7 @@ * - $terms: the themed list of taxonomy term links output from theme_links(). * - $submitted: themed submission information output from * theme_node_submitted(). + * TODO D7 : document $FIELD_NAME_rendered variables. * * Other variables: * - $node: Full node object. Contains data that may not be safe. === modified file 'modules/simpletest/drupal_web_test_case.php' --- modules/simpletest/drupal_web_test_case.php 2009-01-06 13:00:13 +0000 +++ modules/simpletest/drupal_web_test_case.php 2009-01-15 16:50:16 +0000 @@ -157,6 +157,10 @@ $status = $status ? 'pass' : 'fail'; } + if ($status == 'fail') { + $DELETE_THIS_BLOCK = 1; + } + // Increment summary result counter. $this->results['#' . $status]++; @@ -829,7 +833,7 @@ // Add the specified modules to the list of modules in the default profile. $args = func_get_args(); $modules = array_unique(array_merge(drupal_get_profile_modules('default', 'en'), $args)); - drupal_install_modules($modules); + drupal_install_modules($modules, TRUE); // Because the schema is static cached, we need to flush // it between each run. If we don't, then it will contain @@ -1995,4 +1999,58 @@ $match = is_array($code) ? in_array($curl_code, $code) : $curl_code == $code; return $this->assertTrue($match, $message ? $message : t('HTTP response expected !code, actual !curl_code', array('!code' => $code, '!curl_code' => $curl_code)), t('Browser')); } + + /** + * TODO write documentation. + * @param $type + * @param $field_name + * @param $settings + * @return unknown_type + */ + protected function drupalCreateField($type, $field_name = NULL, $settings = array()) { + if (!isset($field_name)) { + $field_name = strtolower($this->randomName()); + } + $field_definition = array( + 'field_name' => $field_name, + 'type' => $type, + ); + $field_definition += $settings; + field_create_field($field_definition); + + $field = field_read_field($field_name); + $this->assertTrue($field, t('Created field @field_name of type @type.', array('@field_name' => $field_name, '@type' => $type))); + + return $field; + } + + /** + * TODO write documentation. + * @param $field_name + * @param $widget_type + * @param $display_type + * @param $bundle + * @return unknown_type + */ + protected function drupalCreateFieldInstance($field_name, $widget_type, $formatter_type, $bundle) { + $instance_definition = array( + 'field_name' => $field_name, + 'bundle' => $bundle, + 'widget' => array( + 'type' => $widget_type, + ), + 'display' => array( + 'full' => array( + 'type' => $formatter_type, + ), + ), + ); + field_create_instance($instance_definition); + + $instance = field_read_instance($field_name, $bundle); + $this->assertTrue($instance, t('Created instance of field @field_name on bundle @bundle.', array('@field_name' => $field_name, '@bundle' => $bundle))); + + return $instance; + } } + === added file 'modules/simpletest/tests/field_test.info' --- modules/simpletest/tests/field_test.info 1970-01-01 00:00:00 +0000 +++ modules/simpletest/tests/field_test.info 2009-01-15 16:50:15 +0000 @@ -0,0 +1,9 @@ +;$Id$ +name = "Field API Test" +description = "Support module for the Field API tests." +core = 7.x +package = testing +dependencies[] = field +files[] = field_test.module +version = VERSION +hidden = TRUE \ No newline at end of file === added file 'modules/simpletest/tests/field_test.module' --- modules/simpletest/tests/field_test.module 1970-01-01 00:00:00 +0000 +++ modules/simpletest/tests/field_test.module 2009-01-15 16:50:15 +0000 @@ -0,0 +1,337 @@ + 'Edit test entity', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_test_edit_field_values'), + 'access arguments' => array('access content'), + 'type' => MENU_NORMAL_ITEM, + ); + + return $items; +} + +/** + * Implementation of hook_theme(). + */ +function field_test_theme() { + return array( + 'field_formatter_field_test_default' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_field_test_multiple' => array( + 'arguments' => array('element' => NULL), + ), + ); +} + +/** + * Define a test fieldable entity. + * This entity has only one instance. + */ +function field_test_fieldable_info() { + $bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle')); + return array( + 'field_test_entity' => array( + 'name' => t('Test Entity'), + 'id key' => 'ftid', + 'revision key' => 'ftvid', + 'cacheable' => FALSE, + 'bundle key' => 'fttype', + 'bundles' => $bundles, + ), + 'field_test_cacheable_entity' => array( + 'name' => t('Test Entity, cacheable'), + 'id key' => 'ftid', + 'revision key' => 'ftvid', + 'cacheable' => TRUE, + 'bundle key' => 'fttype', + 'bundles' => $bundles, + ), + ); +} + +function field_test_create_bundle($bundle, $text) { + $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle')); + $bundles += array($bundle => $text); + variable_set('field_test_bundles', $bundles); + field_attach_create_bundle($bundle); +} + +function field_test_rename_bundle($bundle_old, $bundle_new) { + $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle')); + $bundles[$bundle_new] = $bundles[$bundle_old]; + unset($bundles[$bundle_old]); + variable_set('field_test_bundles', $bundles); + field_attach_rename_bundle($bundle_old, $bundle_new); +} + +function field_test_delete_bundle($bundle) { + $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle')); + unset($bundles[$bundle]); + variable_set('field_test_bundles', $bundles); + field_attach_delete_bundle($bundle); +} + +/** + * Implementation of hook_field_build_modes(). + */ +function field_test_field_build_modes($obj_type) { + $modes = array(); + if ($obj_type == 'field_test_entity') { + $modes = array( + 'full' => t('Full node'), + 'teaser' => t('Teaser'), + ); + } + return $modes; +} + +function field_test_entity_get_entity($id = 1, $vid = 1, $bundle = FIELD_TEST_BUNDLE) { + $entity = new stdClass(); + $entity->ftid = $id; + $entity->ftvid = $vid; + $entity->fttype = $bundle; + + return $entity; +} + +/** + * Form to set the value of fields attached to our entity. + */ +function field_test_edit_field_values() { + $form = array(); + + // Get attached fields/widgets. + $fields = field_info_instances(FIELD_TEST_BUNDLE); + + foreach ($fields as $field) { + $form[$field->name] = field_get_widget($field); + } + + return $form; +} + +/** + * Submit handler for field_test_set_field_values(). + */ +function field_test_edit_field_values_submit($form, &$form_state) { + field_set_value($form_state); +} + +/** + * Get fields attached to our entity. + */ +function field_test_get_field_values() { + // Load attached fields. + $field_values = field_get_field_values('test_entity', FIELD_TEST_ELEMENT_ID); + + return $field_values; +} + + +/** + * Implementation of hook_field_info(). + * + * This field provides a textfield which only accepts the value 1. + */ +function field_test_field_info() { + return array( + 'test_field' => array( + 'label' => t('Test Field'), + 'description' => t('Stores the value 1.'), + 'settings' => array('test_field_setting' => 'dummy test string'), + 'instance_settings' => array('test_instance_setting' => 'dummy test string'), + 'default_widget' => 'test_field_widget', + 'default_formatter' => 'field_test_default', + ), + ); +} + +/** + * Implementation of hook_field_columns(). + */ +function field_test_field_columns($field) { + $columns['value'] = array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => FALSE, + ); + return $columns; +} + +/** + * Implementation of hook_instance_settings(). + */ +function field_test_field_instance_settings($field_type) { + return array('test_instance_setting' => 'dummy test string'); +} + +/** + * Implementation of hook_field_validate(). + */ +function field_test_field_validate(&$obj_type, $object, $field, $instance, &$items, $form) { + if (is_array($items)) { + foreach ($items as $delta => $item) { + $error_element = isset($item['_error_element']) ? $item['_error_element'] : ''; + if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']); + if ($item['value'] != 1) { + form_set_error($error_element, t('%name only accepts the value 1.', array('%name' => $instance['widget']['label']))); + } + } + } + + return $items; +} + +/** + * Implementation of hook_field_sanitize(). + */ +function field_test_field_sanitize($obj_type, $object, $field, $instance, &$items) { + foreach ($items as $delta => $item) { + $value = check_plain($item['value']); + $items[$delta]['safe'] = $value; + } +} + +/** + * Implementation of hook_field_is_empty(). + */ +function field_test_field_is_empty($item, $field) { + return empty($item['value']); +} + +/** + * Implementation of hook_field_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 field_test_field_widget_info() { + return array( + 'test_field_widget' => array( + 'label' => t('Test field'), + 'field types' => array('test_field'), + 'settings' => array('test_widget_setting' => 'dummy test string'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'test_field_widget_multiple' => array( + 'label' => t('Test field 1'), + 'field types' => array('test_field'), + 'settings' => array('test_widget_setting' => 'dummy test string'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + ); +} + +/** + * Implementation of hook_field_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 $insatnce + * the insatnce 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 field_test_field_widget(&$form, &$form_state, $instance, $items, $delta = 0) { + $element = array( + '#type' => 'textfield', + '#default_value' => isset($items[$delta]) ? $items[$delta] : '', + ); + return $element; +} + +/** + * Implementation of hook_field_formatter_info(). + */ +function field_test_field_formatter_info() { + return array( + 'field_test_default' => array( + 'label' => t('Default'), + 'field types' => array('test_field'), + 'settings' => array( + 'test_formatter_setting' => 'dummy test string', + ), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'field_test_multiple' => array( + 'label' => t('Default'), + 'field types' => array('test_field'), + 'settings' => array( + 'test_formatter_setting_multiple' => 'dummy test string', + ), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + ), + ), + ); +} + +/** + * Theme function for 'field_test_default' formatter. + */ +function theme_field_formatter_field_test_default($element) { + $value = $element['#item']['value']; + $settings = $element['#settings']; + + return $settings['test_formatter_setting'] . '|' . $value; +} + +/** + * Theme function for 'field_test_multiple' formatter. + */ +function theme_field_formatter_field_test_multiple($element) { + $settings = $element['#settings']; + + $items = array(); + foreach (element_children($element) as $key) { + $items[$key] = $key .':'. $element[$key]['#item']['value']; + } + $output = implode('|', $items); + return $settings['test_formatter_setting_multiple'] . '|' . $output; +} \ No newline at end of file === modified file 'modules/system/system.admin.inc' --- modules/system/system.admin.inc 2009-01-14 13:00:07 +0000 +++ modules/system/system.admin.inc 2009-01-15 16:50:54 +0000 @@ -578,14 +578,7 @@ cache_clear_all('schema', 'cache'); // Get current list of modules. $files = module_rebuild_cache(); - - // Remove hidden modules from display list. - foreach ($files as $filename => $file) { - if (!empty($file->info['hidden']) || !empty($file->info['required'])) { - unset($files[$filename]); - } - } - + uasort($files, 'system_sort_modules_by_info_name'); // If the modules form was submitted, then system_modules_submit() runs first @@ -604,6 +597,10 @@ // Iterate through each of the modules. foreach ($files as $filename => $module) { + // Do not display hidden modules. + if (!empty($module->info['hidden']) || !empty($module->info['required'])) { + continue; + } $extra = array(); $extra['enabled'] = (bool) $module->status; // If this module requires other modules, add them to the array. === modified file 'modules/user/user-profile.tpl.php' --- modules/user/user-profile.tpl.php 2008-12-06 09:01:58 +0000 +++ modules/user/user-profile.tpl.php 2009-01-15 16:50:16 +0000 @@ -38,7 +38,8 @@ * Available variables: * - $user_profile: All user profile data. Ready for print. * - $profile: Keyed array of profile categories and their items or other data - * provided by modules. + * provided by modules. + * - TODO D7 : document $FIELD_NAME_rendered variables. * * @see template_preprocess_user_profile() */ === modified file 'modules/user/user.module' --- modules/user/user.module 2009-01-13 07:00:10 +0000 +++ modules/user/user.module 2009-01-15 16:50:16 +0000 @@ -83,6 +83,39 @@ ); } +/** + * Implementation of hook_fieldable_info(). + */ +function user_fieldable_info() { + $return = array( + 'user' => array( + 'name' => t('User'), + 'id key' => 'uid', + ), + ); + return $return; +} + +/** + * Implementation of hook_field_build_modes(). + */ +function user_field_build_modes($obj_type) { + $modes = array(); + if ($obj_type == 'user') { + $modes = array( + 'basic' => array( + 'title' => t('Basic'), + 'build modes' => array( + 'full' => array( + 'title' => t('User account'), + ), + ), + ), + ); + } + return $modes; +} + function user_external_load($authname) { $result = db_query("SELECT uid FROM {authmap} WHERE authname = '%s'", $authname); @@ -186,6 +219,13 @@ while ($role = db_fetch_object($result)) { $user->roles[$role->rid] = $role->name; } + + // Attach fields. + // TODO D7 : not sure the 3rd param ($types) is needed. + if (drupal_function_exists('field_attach_load')) { + field_attach_load('user', array($user->uid => $user)); + } + user_module_invoke('load', $array, $user); } else { @@ -233,6 +273,20 @@ unset($edit['pass']); } + // Get the fields form so we can recognize the fields in the $edit + // form that should not go into the serialized data array. + $field_form = array(); + $field_form_state = array(); + $edit = (object) $edit; + if (drupal_function_exists('field_attach_form')) { + field_attach_form('user', $edit, $field_form, $field_form_state); + } + // Presave fields. + if (drupal_function_exists('field_attach_presave')) { + field_attach_presave('user', $edit); + } + $edit = (array) $edit; + if (is_object($account) && $account->uid) { user_module_invoke('update', $edit, $account, $category); $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid))); @@ -242,9 +296,10 @@ $edit['access'] = REQUEST_TIME; } foreach ($edit as $key => $value) { - // Fields that don't pertain to the users or user_roles - // automatically serialized into the users.data column. - if ($key != 'roles' && empty($user_fields[$key])) { + // Form fields that don't pertain to the users, user_roles, or + // Field API are automatically serialized into the users.data + // column. + if ($key != 'roles' && empty($user_fields[$key]) && empty($field_form[$key])) { if ($value === NULL) { unset($data[$key]); } @@ -259,8 +314,14 @@ // Save changes to the users table. $success = drupal_write_record('users', $edit, 'uid'); if (!$success) { - // The query failed - better to abort the save than risk further data loss. - return FALSE; + // The query failed - better to abort the save than risk further + // data loss. + + // TODO: Fields change: I think this is a bug. If no columns in + // the users table are changed, drupal_write_record returns + // FALSE because rowCount() (rows changed) is 0. However, + // non-users data may have been changed, e.g. fields. + // return FALSE; } // Reload user roles if provided. @@ -288,6 +349,12 @@ } } + // Save Field data. + $obj = (object) $edit; + if (drupal_function_exists('field_attach_update')) { + field_attach_update('user', $obj); + } + // Refresh user object. $user = user_load(array('uid' => $account->uid)); @@ -321,13 +388,21 @@ // Build the initial user object. $user = user_load(array('uid' => $edit['uid'])); + $obj = (object) $edit; + if (drupal_function_exists('field_attach_insert')) { + field_attach_insert('user', $obj); + } + user_module_invoke('insert', $edit, $user, $category); // Note, we wait with saving the data column to prevent module-handled // fields from being saved there. $data = array(); foreach ($edit as $key => $value) { - if (($key != 'roles') && (empty($user_fields[$key])) && ($value !== NULL)) { + // Form fields that don't pertain to the users, user_roles, or + // Field API are automatically serialized into the users.data + // column. + if (($key != 'roles') && (empty($user_fields[$key]) && empty($field_form[$key])) && ($value !== NULL)) { $data[$key] = $value; } } @@ -1518,6 +1593,8 @@ _user_password_dynamic_validation(); $admin = user_access('administer users'); + $form = array(); + // Account information: $form['account'] = array('#type' => 'fieldset', '#title' => t('Account information'), @@ -1739,7 +1816,17 @@ */ function user_build_content(&$account) { $edit = NULL; + $account->content = array(); + + // Build fields content. + // TODO D7 : figure out where exactly this needs to go + // TODO D7 : $page / $teaser ?? + if (drupal_function_exists('field_attach_view')) { + $account->content += field_attach_view('user', $account); + } + user_module_invoke('view', $edit, $account); + // Allow modules to modify the fully-built profile. drupal_alter('profile', $account); === modified file 'modules/user/user.pages.inc' --- modules/user/user.pages.inc 2009-01-08 09:00:13 +0000 +++ modules/user/user.pages.inc 2009-01-15 16:50:16 +0000 @@ -174,6 +174,11 @@ } // Collect all profiles to make it easier to print all items at once. $variables['user_profile'] = implode($variables['profile']); + + // Add $FIELD_NAME_rendered variables for fields. + if (drupal_function_exists('field_attach_preprocess')) { + $variables += field_attach_preprocess('user', $variables['account']); + } } /** @@ -236,6 +241,12 @@ $edit = (empty($form_state['values'])) ? (array)$account : $form_state['values']; $form = _user_forms($edit, $account, $category); + + // Attach field widgets. + if (drupal_function_exists('field_attach_form')) { + field_attach_form('user', (object) $edit, $form, $form_state); + } + $form['_category'] = array('#type' => 'value', '#value' => $category); $form['_account'] = array('#type' => 'value', '#value' => $account); $form['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#weight' => 30); @@ -256,6 +267,9 @@ * Validation function for the user account and profile editing form. */ function user_profile_form_validate($form, &$form_state) { + // Validate field widgets. + module_invoke('field', 'attach_validate', 'user', (object) $form_state['values'], $form, $form_state); + user_module_invoke('validate', $form_state['values'], $form_state['values']['_account'], $form_state['values']['_category']); // Validate input to ensure that non-privileged users can't alter protected data. if ((!user_access('administer users') && array_intersect(array_keys($form_state['values']), array('uid', 'init', 'session'))) || (!user_access('administer permissions') && isset($form_state['values']['roles']))) { === added file 'scripts/generate-autoload.pl' --- scripts/generate-autoload.pl 1970-01-01 00:00:00 +0000 +++ scripts/generate-autoload.pl 2009-01-15 16:50:16 +0000 @@ -0,0 +1,72 @@ +#!/usr/bin/perl +# +# Usage: perl generate-autoload.pl [ file ... ] > autoload.inc +# +# Generate function autoloaders for a subset of functions in all the +# listed files. The autloaders are written to standard output and are +# of the form: +# +# /** +# * PHPdoc (not required) +# */ +# function funcname(&$arg1, $arg2 = DEFAULT, ...) { +# if (drupal_function_exists('_funcname')) { +# return _funcname($arg1, $arg, ...); +# } +# throw new FieldException('cannot autoload function _$func'); +# } +# +# Autoloaders are generated for any function name that matches the +# $func_re regexp. The autoloaded functions are REQUIRED to have a +# leading underscore in the name, and the autoloading functions will +# not. +# +# TO DO: +# * option for whether to/type of Exception to throw for load failure +# * option to control @defgroup for PHPdoc +# * option to take function name regexps on the command line + +use strict; + +# Edit this regexp to match any function names for which you want +# autoloaders. Do not include the leading _. +my $func_re = qr/field_attach_.*/; + +my $re = qr| + (?:(^/\*\*\s*\n # start PHPdoc + (?:^\s*\*.*\n)* # any number of comment lines + ^\s*\*/\s*\n))? # end of PHPdoc + ^function\s*_($func_re)(\(.*?\)) # function signature +|xm; + +print "); + while ($body =~ /$re/g) { + my($doc, $func, $func_args) = ($1, $2, $3); + my $pass_args = $func_args; + # remove &'s from passed arguments + $pass_args =~ s/\&//g; + # remove default values from passed arguments + $pass_args =~ s/\s*=.*?(?=[,\)])//g; + # $doc contains a leading and trailing newline. + # $pass_args is wrapped in parens + print " +${doc}function $func$func_args { + if (drupal_function_exists('_$func')) { + return _$func$pass_args; + } + throw new FieldException('cannot autoload function _$func'); +} +"; + } +} === added directory 'sites/all/modules' === added directory 'sites/all/modules/admin_menu' === added file 'sites/all/modules/admin_menu/CHANGELOG.txt' --- sites/all/modules/admin_menu/CHANGELOG.txt 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/CHANGELOG.txt 2009-01-15 16:50:16 +0000 @@ -0,0 +1,200 @@ +// $Id: CHANGELOG.txt,v 1.77 2008/12/17 09:10:05 sun Exp $ + +Admin Menu x.x-x.x, xxxx-xx-xx +------------------------------ + + +Admin Menu 7.x-x.x, xxxx-xx-xx +------------------------------ +#346106 by sun: Fixed XHTML-Strict validation for admin menu icon. +#287448 by sun: Fixed unnecessary menu rebuild for users without permission to + use admin menu. +#342002 by AltaVida: Fixed improper test for node/add paths. +#272920 by keith.smith: Changed all text strings throughout the module. +#337820 by Dave Reid: Updated log out path to "user/logout". +#340546 by Dave Reid: Updated for drupal_add_js(). +#340531 by Dave Reid: Updated for module_list(). +#322731 by sun: Fixed improper use of t() in module install file. +#282030 by sun: Fixed "Run updates" item visible for unprivileged users. +#322877 by sun: Added tweak to move page tabs into administration menu. +#287468 by sun: Fixed module paths directly below "admin" get the wrong parent. +#310423 by sun: Added optional position: fixed configuration setting. +#292657 by smk-ka: Improved rendering performance. +#234149 by yhager, sun: Fixed RTL support for IE. +#266358 by sun: Updated for drupal_add_css(). +#323726 by danez1972: Added Spanish translation. +#325057 by sun: Updated README.txt. +#234149 by yhager, levavie, sun: Added RTL support. +#325057 by sun: Added links to flush specific caches. +#324334 by AltaVida: Fixed usernames with spaces not in Devel user switch links. +#320526 by yettyn, sun: Updated to UNSTABLE-2 (DBTNG queries, permissions, etc). +#319382 by betz: Added Dutch translation. +by sun: Changed admin_menu_wipe() to admin_menu_flush_caches(). +by sun: Updated content-type edit menu item locations. +by sun: Fixed sess_count() changed to drupal_session_count(). + + +Admin Menu 6.x-1.1, 2008-09-12 +------------------------------ +#295476 by pwolanin, use for icon path to fix front-page path-change + bug and pathauto conflict, add wipe button to admin form. +#301370 by sun: Disabled module fieldset collapsing behavior by default. +#288672 by sun: Fixed JS hover behavior not working in IE. +#290803 by sun: Fixed missing devel_themer in devel modules; added some others. +#286636 by sun: Fixed menus do not drop down in IE6. +#249537 by pwolanin, sun: Added admin_menu_suppress() to allow other modules to + disable the display of admin_menu on certain pages (f.e. popups). +#268211 by sun: Fixed invalid issue queue links for custom modules and + sub-modules of projects. +#261461 by sun: Added FAQ entry for displaying other menus like admin_menu. +#264067 by sun: Added FAQ entry for huge amount of anonymous users displayed. +#280002 by pwolanin: Clean up .test setUp function. +#242377 by sun: Fixed sub-level menu items exceed total document height. + + +Admin Menu 6.x-1.0, 2008-06-26 +------------------------------ +#266308 by sun: Fixed jQuery 1.0.x incompatible selector for collapsing modules. +#268373 by sun: Added hook_update to cleanup for alpha/beta testers. +#268373 by sun: Added menu callback to disable/enable developer modules. +#132524 by pwolanin: Fixed admin_menu links are re-inserted each time menu links + are rebuilt. +by smk-ka: Performance: Use 'defer' attribute for JavaScript to delay execution. +#266099 by sun: Fixed description of "Apply margin-top" configuration setting. +#266308 by sun: Usability: Added Utility module features to collapse module + fieldsets on Modules page. +#251341 by sun: Added docs about display drupal links permission. + + +Admin Menu 6.x-1.0-BETA, 2008-06-08 +----------------------------------- +#132524 by sun: Fixed support for sub-content-types below node/add. +#132524 by pwolanin: Added support for localizable menu links. +#132524 by pwolanin, sun: Fixed menu links adjustments. +#132524 by pwolanin: Added simpletest. +#132524 by pwolanin: Major rewrite to better use Drupal 6 menu system. +#132524 by sun: Moved gettext translation files into translations. +#132524 by sun: Committing pre-alpha code for D6 due to public demand. + + +Admin Menu 5.x-2.x, xxxx-xx-xx +------------------------------ +#246221 by sun: Fixed user counter displays different values than Who's online + block. +#239022 by mikl: Added Danish translation. +#234444 by smk-ka: Fixed admin_menu icon does not respect theme settings. +#198240 by sun: Fixed admin_menu displayed in print output. + + +Admin Menu 5.x-2.4, 2008-02-24 +------------------------------ +#214740 by sun: Regression: Fixed directly applied marginTop not supported by IE. +#214725 by sun: Fixed wrong CSS id in admin_menu.js (missed in 5.x-2.3). + + +Admin Menu 5.x-2.3, 2008-02-24 +------------------------------ +#214725 by sun: Fixed CSS id and classes should not contain underscores. +#209390 by sun: Added note about interaction with user role permissions. +#214740 by jjeff, sun: Added module settings to configure margin-top CSS. +#200737 by sun: Changed admin_menu (fav)icon to use theme setting, if defined. +#203116 by smk-ka: Improved performance of non-cached admin_menu by storing + already processed URLs in the cache. +#224605 by sun: 'Add ' items do not appear without 'administer + nodes' permission. +#210615 by robertgarrigos: Fixed Mozilla Mac: Collapsible fieldsets display + error. + + +Admin Menu 5.x-2.2, 2007-01-08 +------------------------------ +#204884 by jjeff: Usability: Override theme font family declaration. +#204935 by jjeff: Added mouseout delay for hovered menus (yay!). +#193941 by downgang: Fixed margin in IE6 using Garland theme. +#197306 by sun: Fixed 'Run updates' leads to wrong url with clean URLs disabled. +Moved images into sub-folder. +by smk-ka: Fixed icon title for user counter not displayed & coding style. +Fixed user count not displayed without 'administer users' permission. + + +Admin Menu 5.x-2.1, 2007-12-02 +------------------------------ +Fixed adding menu items with negative weight not always working. +Fixed admin_menu_copy_items() is overwriting already existing items. +Fixed display menu item ids in devel settings does not work. + + +Admin Menu 5.x-2.0, 2007-12-02 +------------------------------ +Added devel_admin_menu() for fast access to clear-cache, variable editor and + switch_user. +Added username to logout button. +Added hook_admin_menu() to allow other modules to alter admin_menu. +#194189 by sun: Added counter for current anonymous/authenticated users. +Added Drupal.org project issue queue links for all enabled contrib modules. +#189701 by sun: Changed admin_menu icon to be a menu. +#193925 by sun: Removed obsolete menu slicing code. +#193669 by smk-ka: Moved admin_menu builder functions into include file. + + +Admin Menu 5.x-1.2, 2007-11-18 +------------------------------ +#176969 by smk-ka: Fixed performance issues with path(auto) module by + introducing a menu cache for admin_menu. +#179648 by sun: Inject admin_menu into theme. + Fixes several CSS bugs in various themes and also activation of admin_menu + immediately after installation. +#191213 by Standard: Fixed block info shouldn't contain the word "block". +#187816 by sun: Fixed "Add" not translatable. +#186218 by sun: Fixed admin menu icon too big in Safari. +#182563 by sun: Fixed wrong datatype for array_search in _admin_menu_get_children(). +#183496 by sun: Fixed invalid argument supplied for foreach in admin_menu_copy_items(). + + +Admin Menu 5.x-1.1, 2007-10-10 +------------------------------ +#178876 by sun: Fixed 3rd-level submenus expand without hover over. +#153455 by sun: Fixed add product node sub-elements are empty. +Fixed path_to_theme() call breaking blocks page. +#177582 by sun: Fixed bluebreeze theme compatibility. + + +Admin Menu 5.x-1.0, 2007-09-06 +------------------------------ +#156952 by sun: Fixed admin menu inaccessible due to margins. +#149229 by sun: Fixed admin menu not expanding in IE7. +#172545 by sun: Use opacity instead of -moz-opacity. +#132867 Fixed z-index too low. +- Fixed admin menu block selectors to override any other theme styles. +#155589 by sun: Added permission to access administration menu. +- Fixed a PHP warning when there are no content types defined in the system, as + node/add then has no child menu items. +#155312 by sun: Fixed menu item tooltip clashes. +Added support for custom stylesheets per theme. +Removed 4.7.x compatibility. + + +Admin Menu 4.7-1.3, 2007-03-30 +------------------------------ +#126601 Fixed Users can see inaccessible items. +#121027 Fixed Page not found entries for menu-collapsed.png. + + +Admin Menu 4.7-1.2, 2007-03-04 +------------------------------ +- Fixed menu item adjustments +- Fixed IE / Safari support +- Fixed base_path for IE support +- Added create content options to content management menu + + +Admin Menu 4.7-1.1, 2007-01-24 +------------------------------ +First stable release, compatible to Drupal 4.7.x and 5.x. + + +Admin Menu 4.7-1.0, 2007-01-16 +------------------------------ +Initial release of admin_menu module. Already supporting Drupal 5.0. + + === added file 'sites/all/modules/admin_menu/LICENSE.txt' --- sites/all/modules/admin_menu/LICENSE.txt 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/LICENSE.txt 2009-01-15 16:50:15 +0000 @@ -0,0 +1,274 @@ +GNU GENERAL PUBLIC LICENSE + + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, +Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute +verbatim copies of this license document, but changing it is not allowed. + + Preamble + +The licenses for most software are designed to take away your freedom to +share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free software--to +make sure the software is free for all its users. This General Public License +applies to most of the Free Software Foundation's software and to any other +program whose authors commit to using it. (Some other Free Software +Foundation software is covered by the GNU Library General Public License +instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the +freedom to distribute copies of free software (and charge for this service if +you wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs; and that +you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must give the recipients all the rights that you have. You must make +sure that they, too, receive or can get the source code. And you must show +them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems +introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND + MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such +program or work, and a "work based on the Program" means either the +Program or any derivative work under copyright law: that is to say, a work +containing the Program or a portion of it, either verbatim or with +modifications and/or translated into another language. (Hereinafter, translation +is included without limitation in the term "modification".) Each licensee is +addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program is +not restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made +by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the +Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such +modifications or work under the terms of Section 1 above, provided that you +also meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or in +part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print such +an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be +reasonably considered independent and separate works in themselves, then +this License, and its terms, do not apply to those sections when you distribute +them as separate works. But when you distribute the same sections as part +of a whole which is a work based on the Program, the distribution of the +whole must be on the terms of this License, whose permissions for other +licensees extend to the entire whole, and thus to each and every part +regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to +work written entirely by you; rather, the intent is to exercise the right to +control the distribution of derivative or collective works based on the +Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of a +storage or distribution medium does not bring the other work under the scope +of this License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 +and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source +code, which must be distributed under the terms of Sections 1 and 2 above +on a medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for +noncommercial distribution and only if you received the program in object +code or executable form with such an offer, in accord with Subsection b +above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source code +means all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation and +installation of the executable. However, as a special exception, the source +code distributed need not include anything that is normally distributed (in +either source or binary form) with the major components (compiler, kernel, +and so on) of the operating system on which the executable runs, unless that +component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with the +object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, +modify, sublicense or distribute the Program is void, and will automatically +terminate your rights under this License. However, parties who have received +copies, or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the +Program (or any work based on the Program), you indicate your acceptance +of this License to do so, and all its terms and conditions for copying, +distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these terms and +conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution +of the Program by all those who receive copies directly or indirectly through +you, then the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In such +case, this License incorporates the limitation as if written in the body of this +License. + +9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will be +similar in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that +version or of any later version published by the Free Software Foundation. If +the Program does not specify a version number of this License, you may +choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make +exceptions for this. Our decision will be guided by the two goals of +preserving the free status of all derivatives of our free software and of +promoting the sharing and reuse of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT +PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL +NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR +AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR +ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE +LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, +SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES +SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN +IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF +THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS === added file 'sites/all/modules/admin_menu/README.txt' --- sites/all/modules/admin_menu/README.txt 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/README.txt 2009-01-15 16:50:16 +0000 @@ -0,0 +1,162 @@ +/* $Id: README.txt,v 1.26 2008/11/30 12:50:23 sun Exp $ */ + +-- SUMMARY -- + +The Drupal administration menu module displays the entire administrative +menu tree (and most local tasks) in a drop-down menu, providing administrators +one- or two-click access to most pages. Other modules may also add menu +links to administration menu using hook_admin_menu(). + +For a full description of the module, visit the project page: + http://drupal.org/project/admin_menu + +To submit bug reports and feature suggestions, or to track changes: + http://drupal.org/project/issues/admin_menu + + +-- REQUIREMENTS -- + +None. + + +-- INSTALLATION -- + +* Install as usual, see http://drupal.org/node/70151 for further information. + + +-- CONFIGURATION -- + +* Configure user permissions in Administer >> User management >> Permissions >> + admin_menu module: + + - access administration menu + + Users in roles with the "access administration menu" permission will see + the administration menu at the top of each page. + + - display drupal links + + Users in roles with the "display drupal links" permission will receive + links to Drupal.org issue queues for all enabled contributed modules. The + issue queue links appear under the administration menu icon.) + + Note that the menu items displayed in the administration Menu depend on the + actual permissions of the viewing user. For example, the "User management" + menu item is not displayed to a user who is not a member of a role with the + "administer access control" and "administer users" permissions. + +* Customize the menu settings in Administer >> Site configuration >> + Administration menu. + +* To prevent administrative menu items from appearing twice, you may hide the + "Navigation" menu block, or move the "Administer" menu items into a separate + menu. + + +-- CUSTOMIZATION -- + +* To override the default administration menu icon, you may: + + 1) Disable it via CSS in your theme: + + body #admin-menu-icon { display: none; } + + 2) Alter the image by overriding the theme function: + + Copy the entire theme_admin_menu_icon() function into your template.php, + rename it to phptemplate_admin_menu_icon() or THEMENAME_admin_menu_icon(), + and customize the output according to your needs. + + Remember that the output of the administration menu is cached. To see changes + from your theme override function, you must clear your site cache (via + the "Flush all caches" link on the menu). + +* To override the font size, add the following line to your theme's stylesheet: + + body #admin-menu { font-size: 10px; } + + +-- TROUBLESHOOTING -- + +* If the menu does not display, check the following: + + - Are the "access administration menu" and "access administration pages" + permissions enabled for the appropriate roles? + + - Does your theme output the $closure variable? + +* If the menu is rendered behind a Flash movie object, add this property to your + Flash object(s): + + + + See http://drupal.org/node/195386 for further information. + + +-- FAQ -- + +Q: When the administration menu module is enabled, blank space is added to the + bottom of my theme. Why? + +A: This is caused by a long list of links to module issue queues at Drupal.org. + Use Administer >> User management >> Permissions to disable the "display + drupal links" permission for all appropriate roles. Note that since UID 1 + automatically receives all permissions, the list of issue queue links cannot + be disabled for UID 1. + + +Q: After upgrading to 6.x-1.x, the menu disappeared. Why? + +A: You may need to regenerate your menu. Visit + http://example.com/admin/build/modules to regenerate your menu (substitute + your site name for example.com). + + +Q: Can I configure the administration menu module to display another menu (like + the Navigation menu, for instance)? + +A: No. As the name implies, administration menu module is for administrative + menu links only. However, you can copy and paste the contents of + admin_menu.css into your theme's stylesheet and replace #admin-menu with any + other menu block id (#block-menu-1, for example). + + +Q: Sometimes, the user counter displays a lot of anonymous users, but no spike + of users or requests appear in Google Analytics or other tracking tools. + +A: If your site was concurrently spidered by search-engine robots, it may have + a significant number of anonymous users for a short time. Most web tracking + tools like Google Analytics automatically filter out these requests. + + +Q: I enabled "Aggregate and compress CSS files", but admin_menu.css is still + there. Is this normal? + +A: Yes, this is the intended behavior. the administration menu module only loads + its stylesheet as needed (i.e., on page requests by logged-on, administrative + users). + + +-- CONTACT -- + +Current maintainers: +* Daniel F. Kudwien (sun) - http://drupal.org/user/54136 +* Peter Wolanin (pwolanin) - http://drupal.org/user/49851 +* Stefan M. Kudwien (smk-ka) - http://drupal.org/user/48898 +* Dave Reid (Dave Reid) - http://drupal.org/user/53892 + +Major rewrite for Drupal 6 by Peter Wolanin (pwolanin). + +This project has been sponsored by: +* UNLEASHED MIND + Specialized in consulting and planning of Drupal powered sites, UNLEASHED + MIND offers installation, development, theming, customization, and hosting + to get you started. Visit http://www.unleashedmind.com for more information. + +* Lullabot + Friendly Drupal experts providing professional consulting & education + services. Visit http://www.lullabot.com for more information. + +* Acquia + Commercially Supported Drupal. Visit http://acquia.com for more information. + === added file 'sites/all/modules/admin_menu/admin_menu-rtl.css' --- sites/all/modules/admin_menu/admin_menu-rtl.css 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/admin_menu-rtl.css 2009-01-15 16:50:15 +0000 @@ -0,0 +1,26 @@ +/* $Id: admin_menu-rtl.css,v 1.2 2008/10/29 20:01:48 sun Exp $ */ + +#admin-menu { text-align: right; direction: rtl; } +#admin-menu li.admin-menu-action { float: left; } +#admin-menu li.admin-menu-action a { border-right: 1px solid #323232; border-left: none; } + +/* all lists */ +#admin-menu ul a { border-left: 1px solid #323232; border-right: 0; padding: 4px 8px; text-align: right; } + +/* all list items */ +/* width needed or else Opera goes nuts */ +#admin-menu li { float: right; position: relative; } +#admin-menu li li { position: static; } +#admin-menu li ul { right: 0; } + +/* third-and-above-level lists */ +#admin-menu li li.expandable ul { margin: -20px 160px 0 0; } + +#admin-menu li.admin-menu-action:hover ul { + left: 0 !important; + right: auto; +} + +/* second-and-more-level hovering */ +#admin-menu li li.expandable { background: #45454a url(images/arrow-rtl.png) no-repeat 5px 7px; } + === added file 'sites/all/modules/admin_menu/admin_menu.css' --- sites/all/modules/admin_menu/admin_menu.css 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/admin_menu.css 2009-01-15 16:50:15 +0000 @@ -0,0 +1,103 @@ +/* $Id: admin_menu.css,v 1.29 2008/11/02 21:36:09 sun Exp $ */ +/** + * Administration Menu. + * + * Implementation of Sons of Suckerfish Dropdowns. + * @see www.htmldog.com/articles/suckerfish + **/ +#admin-menu { position: absolute; top: 0px; left: 0px; font-size: 9px; font-family: "lucida grande", tahoma, verdana, arial, sans-serif; background: url(images/bkg.png) bottom left repeat-x #101010; width: 100%; text-align: left; } +#admin-menu li.admin-menu-icon a { padding: 1px 8px 4px; } +#admin-menu li.admin-menu-icon ul a { padding: 4px 8px; } +#admin-menu li.admin-menu-icon img { vertical-align: bottom; } +#admin-menu li.admin-menu-users img { margin-top: 1px; } +#admin-menu li.admin-menu-action { float: right; } +#admin-menu li.admin-menu-action a { border-left: 1px solid #323232; border-right: none; } +body.admin-menu { margin-top: 20px !important; } + +/* all lists */ +#admin-menu, #admin-menu ul { padding: 0; margin: 0; list-style: none; line-height: 1.4em; z-index: 999; } +#admin-menu ul { position: static; } +#admin-menu ul a { display: block; border-right: 1px solid #323232; border-bottom: none; padding: 4px 8px; font-weight: normal; color: #eee; text-decoration: none; text-align: left; } +#admin-menu ul li.admin-menu-tab a { border-right: 1px solid #52565e; } +#admin-menu li li a { border-right: none; border-top: 1px solid #323232; } + +/* all list items */ +/* width needed or else Opera goes nuts */ +#admin-menu li { float: left; height: 100%; margin: 0 !important; padding: 0; list-style-image: none; list-style-type: none; background-image: none; } +#admin-menu li.admin-menu-tab { padding-bottom: 1px; background: url(images/bkg_tab.png) repeat-x left bottom; } +#admin-menu li li { width: 160px; background: #202020; filter:Alpha(opacity=88); opacity: 0.88; } + +/* second-level lists */ +/* Note: Use left instead of display to hide publicly visible menus because display: none isn't read by screen readers */ +#admin-menu li ul { position: absolute; background: none; margin: 0; width: 160px; left: -999em; display: none; line-height: 1.2em; } + +/* third-and-above-level lists */ +#admin-menu li li.expandable ul { margin: -20px 0 0 160px; } + +#admin-menu li:hover ul ul, +#admin-menu li:hover ul ul ul, +#admin-menu li:hover ul ul ul ul, +#admin-menu li:hover ul ul ul ul ul, +#admin-menu li.iehover ul ul, +#admin-menu li.iehover ul ul ul, +#admin-menu li.iehover ul ul ul ul, +#admin-menu li.iehover ul ul ul ul ul { + left: -999em; + display: none; +} + +/* lists nested under hovered list items */ +#admin-menu li:hover ul, +#admin-menu li li:hover ul, +#admin-menu li li li:hover ul, +#admin-menu li li li li:hover ul, +#admin-menu li li li li li:hover ul, +#admin-menu li.iehover ul, +#admin-menu li li.iehover ul, +#admin-menu li li li.iehover ul, +#admin-menu li li li li.iehover ul, +#admin-menu li li li li li.iehover ul { + left: auto; + display: block; +} +#admin-menu li.admin-menu-action:hover ul { + right: 0; +} + +/* second-and-more-level hovering */ +#admin-menu li li.expandable { background: #45454a url(images/arrow.png) no-repeat 145px 7px; } +#admin-menu li li:hover, +#admin-menu li li.iehover { + background-color: #111; +} +#admin-menu li li:hover a, +#admin-menu li li:hover li:hover a, +#admin-menu li li:hover li:hover li:hover a { + color: #fff; +} +#admin-menu li li.expandable:hover a, +#admin-menu li li.expandable:hover li.expandable:hover a { + border-color: #666666; color: #eee; +} +#admin-menu li li.expandable:hover li a, +#admin-menu li li.expandable:hover li.expandable:hover li a { + border-color: #323232; +} +#admin-menu li li:hover li a, +#admin-menu li li.iehover li a, +#admin-menu li li.iehover li.iehover li a { + color: #eee; +} +#admin-menu li li.iehover a, +#admin-menu li li.iehover li.iehover a, +#admin-menu li li.iehover li.iehover li.iehover a { + color: #fff; width: 90%; /* IE */ +} + +/* #210615: Mozilla on Mac fix */ +html.js fieldset.collapsible div.fieldset-wrapper { overflow: visible; } + +@media print { + #admin-menu { display: none; } + body.admin-menu { margin-top: 0 !important; } +} === added file 'sites/all/modules/admin_menu/admin_menu.inc' --- sites/all/modules/admin_menu/admin_menu.inc 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/admin_menu.inc 2009-01-15 16:50:16 +0000 @@ -0,0 +1,529 @@ + $item) { + // Exclude menu callbacks, include items below admin/* and node/add/*. + if ($item['type'] != MENU_CALLBACK && (($item['_parts'][0] == 'admin' && count($item['_parts']) > 1) || (strpos($path, 'node/add') === 0))) { + // TODO: handle local tasks with wildcards + if (!strpos($path, '%')) { + $item = admin_menu_link_build($item); + $menu_links[$path] = $item; + $sort[$path] = $item['_number_parts']; + } + } + } + $deleted = admin_menu_adjust_items($menu_links, $sort); + if ($menu_links) { + // Make sure no child comes before its parent. + array_multisort($sort, SORT_NUMERIC, $menu_links); + + foreach ($menu_links as $item) { + admin_menu_link_save($item); + } + } + // Allow modules to add more links. If you want to alter links saved by + // admin_menu, use hook_menu_link_alter() and look for + // $item['module'] == 'admin_menu' + $links = module_invoke_all('admin_menu', $deleted); + foreach ($links as $item) { + admin_menu_link_save($item); + } +} + +/** + * Prepare a menu link from basic information formatted for a router item. + */ +function admin_menu_link_build($item) { + $item['module'] = 'admin_menu'; + $item['menu_name'] = 'admin_menu'; + $item += array( + 'link_title' => isset($item['title']) ? $item['title'] : '', + 'link_path' => $item['path'], + 'hidden' => 0, + 'options' => array(), + ); + $item['options']['alter'] = TRUE; + // DAM does not output item descriptions to prevent mouseover clashes and + // increase page loading performance. However, the following code shows how + // link attributes can be added (for ajaxified DAM functionality later). + /* + if (!empty($item['description'])) { + $item['options']['attributes']['title'] = $item['description']; + } + */ + if (!empty($item['query'])) { + $item['options']['query'] = $item['query']; + } + return $item; +} + +/** + * Convenience function that looks up the plid if $item['parent_path'] is set. + */ +function admin_menu_link_save($item) { + $item = admin_menu_link_build($item); + + // Check whether we are able to update an existing item. + $existing_item = db_query("SELECT mlid, plid, has_children FROM {menu_links} WHERE link_path = :link_path AND menu_name = :menu_name", array(':link_path' => $item['link_path'], ':menu_name' => 'admin_menu'))->fetch(PDO::FETCH_ASSOC); + if ($existing_item) { + $item['mlid'] = $existing_item['mlid']; + $item['plid'] = $existing_item['plid']; + $item['has_children'] = $existing_item['has_children']; + } + + // Look up the parent path for both new and existing links, since the parent + // may change. + if (isset($item['parent_path'])) { + if ($item['parent_path'] == '') { + // means that we want the link at the top level. + $item['plid'] = 0; + } + else { + $plid = db_query("SELECT mlid from {menu_links} WHERE link_path = :link_path AND menu_name = :menu_name", array(':link_path' => $item['parent_path'], ':menu_name' => 'admin_menu'))->fetchField(); + if ($plid) { + $item['plid'] = $plid; + } + } + } + + menu_link_save($item); +} + +/** + * Implementation of hook_admin_menu(). + * + * @param &$deleted + * Array of links under admin/* that were removed by admin_menu_adjust_items(). + * If one of these links is added back, it should be removed from the array. + */ +function admin_menu_admin_menu(&$deleted) { + global $base_url; + + $links = array(); + $icon_path = ''; + + // Add system update links. + $links[] = array( + 'title' => 'Run cron', + 'path' => 'admin/reports/status/run-cron', + 'weight' => 50, + 'parent_path' => $icon_path, + ); + $links[] = array( + 'title' => 'Run updates', + 'path' => $base_url .'/update.php', + 'weight' => 50, + 'parent_path' => $icon_path, + ); + + // Move 'By module' item into Site configuration. + if (isset($deleted['admin/by-module'])) { + $deleted['admin/by-module']['parent_path'] = 'admin/settings'; + $deleted['admin/by-module']['weight'] = -10; + $links[] = $deleted['admin/by-module']; + unset($deleted['admin/by-module']); + } + + // Add link to drupal.org. + $links[] = array( + 'title' => 'Drupal.org', + 'path' => 'http://drupal.org', + 'weight' => 100, + 'parent_path' => $icon_path, + ); + // Add links to project issue queues. + $links[] = array( + 'title' => 'Drupal issue queue', + 'path' => 'http://drupal.org/project/issues/drupal', + 'weight' => -10, + 'parent_path' => 'http://drupal.org', + ); + $projects = array(); + foreach (module_list(FALSE, TRUE) as $module) { + $info = drupal_parse_info_file(drupal_get_path('module', $module) .'/'. $module .'.info'); + if (!isset($info['project']) || (isset($info['project']) && ($info['project'] == 'drupal' || isset($projects[$info['project']])))) { + continue; + } + $projects[$info['project']] = 1; + $url = 'http://drupal.org/project/issues/'. $info['project']; + // Filtering project versions via query string is not yet supported. + // @see http://drupal.org/node/97569 + // $url .= !empty($info['version']) ? '/'. $info['version'] : ''; + $links[] = array( + 'title' => check_plain($info['name']) . ' issue queue', + 'path' => $url, + 'parent_path' => 'http://drupal.org', + ); + } + + // Add 'Create ' items to Content management menu. + if (isset($deleted['node/add'])) { + $deleted['node/add']['parent_path'] = 'admin/content'; + $deleted['node/add']['weight'] = 0; + $links[] = $deleted['node/add']; + unset($deleted['node/add']); + } + foreach($deleted as $path => $item) { + if (strpos($path, 'node/add') !== FALSE) { + $links[] = $deleted[$path]; + unset($deleted[$path]); + } + } + // Make sure longer paths are after shorter ones + ksort($deleted); + foreach (node_get_types('types', NULL, TRUE) as $type) { + $type_url_str = str_replace('_', '-', $type->type); + $type_path = 'admin/build/node-type/' . $type_url_str; + $links[$type_path] = array( + 'title' => 'Edit @content-type', + 'path' => $type_path, + 'parent_path' => 'admin/build/types', + 'options' => array('t' => array('@content-type' => $type->name)), + ); + unset($deleted['admin/build/node-type/' . $type_url_str]); + // CCK and other modules adding to node types handled here. + foreach($deleted as $path => $item) { + if (strpos($path, $type_path) !== FALSE) { + // Logically, parent path can never go shorter than $type_path. + $i = $item['_number_parts'] - 1; + do { + $parent_path = implode('/', array_slice($item['_parts'], 0, $i)); + --$i; + } while (!isset($links[$parent_path]) && $i); + $item['parent_path'] = $parent_path; + $links[$path] = $item; + unset($deleted[$path]); + } + } + } + + // Add clear-cache. + $links[] = array( + 'title' => 'Flush all caches', + 'path' => 'admin_menu/flush-cache', + 'query' => 'destination', + 'weight' => 20, + 'parent_path' => $icon_path, + ); + $caches = array( + 'admin_menu' => 'Administration menu', + 'cache' => 'Cache tables', + 'menu' => 'Menu', + 'requisites' => 'Page requisites', + 'theme' => 'Theme registry', + ); + foreach ($caches as $name => $title) { + $links[] = array( + 'title' => $title, + 'path' => 'admin_menu/flush-cache/' . $name, + 'query' => 'destination', + 'parent_path' => 'admin_menu/flush-cache', + ); + } + + // Add devel module links + if (module_exists('devel')) { + // Add variable editor. + $links[] = array( + 'title' => 'Variable editor', + 'path' => 'devel/variable', + 'weight' => 20, + 'parent_path' => $icon_path, + ); + // Add switch_user items. + if ($devel_user_links = module_invoke('devel', 'switch_user_list')) { + foreach ($devel_user_links as $link) { + if (preg_match('!href="'. base_path() .'([^\?]+)\?([^"]+)" title="([^"]+)">(()?[^<]+()?)!', $link, $match)) { + $links[] = array( + 'title' => $match[4], + 'description' => $match[3], + 'path' => urldecode($match[1]), + 'weight' => 20, + 'query' => 'destination', + 'parent_path' => 'user/logout', + 'options' => array('html' => TRUE), + ); + } + } + } + } + // Add developer modules toggle link. + $saved_state = variable_get('admin_menu_devel_modules_enabled', NULL); + $links[] = array( + 'title' => isset($saved_state) ? t('Enable developer modules') : t('Disable developer modules'), + 'path' => 'admin_menu/toggle-modules', + 'weight' => 88, + 'parent_path' => $icon_path, + 'options' => array('query' => 'destination'), + ); + + return $links; +} + +/** + * Add some hard-coded features for better user experience. + * + * @param array $menu_links + * An array containing the complete administration menu structure, passed by + * reference. + * @param array $sort + * An array containing the # parts of each link - must be updated if a link + * is added. + * @return + * An array of links that were removed from $menu_links. + */ +function admin_menu_adjust_items(&$menu_links, &$sort) { + global $user, $base_url; + $links = array(); + $deleted = array(); + + // Change or remove items, or add new top-level items. + $deleted['admin/by-module'] = $menu_links['admin/by-module']; + unset($menu_links['admin/by-module'], $sort['admin/by-module']); + $deleted['admin/by-task'] = $menu_links['admin/by-task']; + unset($menu_links['admin/by-task'], $sort['admin/by-task']); + + // Remove certain links to re-position them in admin_menu_admin_menu(). + foreach ($menu_links as $path => $link) { + // Remove links below + // - admin/build/node-type/* + // - node/add* + if (strpos($path, 'admin/build/node-type/') !== FALSE || strpos($path, 'node/add') !== FALSE) { + $deleted[$path] = $link; + unset($menu_links[$path], $sort[$path]); + } + } + // Add the icon containing special links. + $links[] = array( + 'title' => theme('admin_menu_icon'), + 'path' => '', + 'weight' => -100, + 'parent_path' => '', + 'options' => array('extra class' => 'admin-menu-icon', 'html' => TRUE), + ); + // Add link to show current authenticated/anonymous users - we will add the + // data dynamically in the _alter hook. + $links[] = array( + 'title' => 'icon_users', + 'path' => 'user', + 'weight' => -90, + 'parent_path' => '', + 'options' => array('extra class' => 'admin-menu-action admin-menu-icon admin-menu-users', 'html' => TRUE), + ); + $links[] = array( + 'title' => 'Log out @username', + 'path' => 'user/logout', + 'weight' => -100, + 'parent_path' => '', + // Note: @username is dynamically replaced by default, we just invoke + // replacement by setting the 't' key here. + 'options' => array('extra class' => 'admin-menu-action admin-menu-logout', 't' => array()), + ); + foreach ($links as $item) { + $path = $item['path']; + $item = admin_menu_link_build($item); + $menu_links[$path] = $item; + $sort[$path] = 1; + } + + return $deleted; +} + +/** + * Form builder function for module settings. + */ +function admin_menu_theme_settings() { + $form['admin_menu_margin_top'] = array( + '#type' => 'checkbox', + '#title' => t('Adjust top margin'), + '#default_value' => variable_get('admin_menu_margin_top', 1), + '#description' => t('If enabled, the site output is shifted down approximately 20 pixels from the top of the viewport to display the administration menu. If disabled, some absolute- or fixed-positioned page elements may be covered by the administration menu.'), + ); + $form['admin_menu_position_fixed'] = array( + '#type' => 'checkbox', + '#title' => t('Keep menu at top of page'), + '#default_value' => variable_get('admin_menu_position_fixed', 0), + '#description' => t('If enabled, the administration menu is always displayed at the top of the browser viewport (even after the page is scrolled). Note: In some browsers, this setting results in a malformed page, an invisible cursor, non-selectable elements in forms, or other issues. Disable this option if these issues occur.'), + ); + $form['tweaks'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced settings'), + ); + $form['tweaks']['admin_menu_tweak_modules'] = array( + '#type' => 'checkbox', + '#title' => t('Collapse fieldsets on modules page'), + '#default_value' => variable_get('admin_menu_tweak_modules', 0), + '#description' => t('If enabled, fieldsets on the modules page are automatically collapsed when loading the page.', array('!modules-url' => url('admin/build/modules'))), + ); + if (module_exists('util')) { + $form['tweaks']['admin_menu_tweak_modules']['#description'] .= '
'. t('If the Utility module was installed for this purpose, it can be safely disabled and uninstalled.') .''; + } + $form['tweaks']['admin_menu_tweak_tabs'] = array( + '#type' => 'checkbox', + '#title' => t('Move local tasks into menu'), + '#default_value' => variable_get('admin_menu_tweak_tabs', 0), + '#description' => t('If enabled, the tabs on the current page are moved into the administration menu. This feature is only available in themes that use the CSS classes tabs primary and tabs secondary for tabs.'), + ); + $form = system_settings_form($form); + $form['wipe description'] = array( + '#type' => 'item', + '#value' => t('If the administration menu displays duplicate menu items, you may need to rebuild your menu items using the Wipe and rebuild button.'), + ); + $form['wipe'] = array( + '#type' => 'submit', + '#value' => t('Wipe and rebuild'), + '#submit' => array('admin_menu_flush_caches'), + ); + + return $form; +} + +/** + * Implementation of hook_flush_caches(). + * + * Wipe the menu so it can be rebuilt from scratch. + */ +function admin_menu_flush_caches() { + db_delete('menu_links')->condition('menu_name', 'admin_menu')->execute(); + menu_cache_clear('admin_menu'); + variable_set('admin_menu_rebuild_links', TRUE); +} + +/** + * Helper function for admin_menu_form_devel_admin_settings_alter(). + * + * Extends Devel module with Administration Menu developer settings. + */ +function _admin_menu_devel_settings_form_alter(&$form, $form_state) { + // Shift system_settings_form buttons. + $weight = isset($form['buttons']['#weight']) ? $form['buttons']['#weight'] : 0; + $form['buttons']['#weight'] = $weight + 1; + + $form['admin_menu'] = array( + '#type' => 'fieldset', + '#title' => t('Administration menu settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $display_options = array('mid', 'weight', 'pid'); + $display_options = array(0 => t('None'), 'mlid' => t('Menu link ID'), 'weight' => t('Weight'), 'plid' => t('Parent link ID')); + $form['admin_menu']['admin_menu_display'] = array( + '#type' => 'radios', + '#title' => t('Display additional data for each menu item'), + '#default_value' => variable_get('admin_menu_display', 0), + '#options' => $display_options, + '#description' => t('Display the selected items next to each menu item link.'), + ); + $form['admin_menu']['admin_menu_show_all'] = array( + '#type' => 'checkbox', + '#title' => t('Display all menu items'), + '#default_value' => variable_get('admin_menu_show_all', 0), + '#description' => t('If enabled, all menu items are displayed regardless of your site permissions. Note: Do not enable on a production site.'), + ); +} + +/** + * Menu callback to enable/disable developer modules. + * + * This saves up to 150ms on each uncached page request. Not much, but + * on larger Drupal sites this is actually a 10% performance increase. + */ +function admin_menu_toggle_modules() { + $rebuild = FALSE; + $saved_state = variable_get('admin_menu_devel_modules_enabled', NULL); + if (isset($saved_state)) { + // Re-enable modules that were enabled before. + module_enable($saved_state); + variable_del('admin_menu_devel_modules_enabled'); + drupal_set_message(t('Enabled these modules: !module-list.', array('!module-list' => implode(', ', $saved_state)))); + $rebuild = TRUE; + } + else { + // Allow site admins to override this variable via settings.php. + $devel_modules = variable_get('admin_menu_devel_modules', array('cache_disable', 'coder', 'content_copy', 'debug', 'delete_all', 'demo', 'devel', 'devel_node_access', 'devel_themer', 'macro', 'form_controller', 'imagecache_ui', 'journal', 'trace', 'upgrade_status', 'user_display_ui', 'util', 'views_ui', 'views_theme_wizard')); + // Store currently enabled modules in a variable. + $devel_modules = array_intersect(module_list(FALSE, FALSE), $devel_modules); + if (!empty($devel_modules)) { + variable_set('admin_menu_devel_modules_enabled', $devel_modules); + // Disable developer modules. + module_disable($devel_modules); + drupal_set_message(t('Disabled these modules: !module-list.', array('!module-list' => implode(', ', $devel_modules)))); + $rebuild = TRUE; + } + else { + drupal_set_message(t('No developer modules are enabled.')); + } + } + if ($rebuild) { + // Make sure everything is rebuilt, basically a combination of the calls + // from system_modules() and system_modules_submit(). + drupal_theme_rebuild(); + menu_rebuild(); + cache_clear_all('schema', 'cache'); + cache_clear_all(); + drupal_clear_css_cache(); + drupal_clear_js_cache(); + // Synchronize to catch any actions that were added or removed. + actions_synchronize(); + } + drupal_goto(''); +} + +/** + * Flush all caches or a specific one. + * + * @param $name + * (optional) Name of cache to flush. + */ +function admin_menu_flush_cache($name = NULL) { + switch ($name) { + case 'admin_menu': + admin_menu_flush_caches(); + break; + + case 'cache': + // Don't clear cache_form - in-progress form submissions may break. + // Ordered so clearing the page cache will always be the last action. + $core = array('cache', 'cache_block', 'cache_filter', 'cache_page'); + $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); + foreach ($cache_tables as $table) { + cache_clear_all('*', $table, TRUE); + } + break; + + case 'menu': + module_invoke('menu', 'rebuild'); + break; + + case 'requisites': + // Change query-strings on css/js files to enforce reload for all users. + _drupal_flush_css_js(); + + drupal_clear_css_cache(); + drupal_clear_js_cache(); + break; + + case 'theme': + module_invoke('system', 'theme_data'); + drupal_theme_rebuild(); + break; + + default: + // Flush all caches; no need to re-implement this. + if (drupal_function_exists('system_clear_cache_submit')) { + $form_state = array(); + system_clear_cache_submit($form_state, NULL); + } + break; + } + drupal_goto(); +} + === added file 'sites/all/modules/admin_menu/admin_menu.info' --- sites/all/modules/admin_menu/admin_menu.info 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/admin_menu.info 2009-01-15 16:50:15 +0000 @@ -0,0 +1,15 @@ +; $Id: admin_menu.info,v 1.9 2008/11/30 12:50:23 sun Exp $ +name = Administration menu +description = Provides a dropdown menu to most administrative tasks and other common destinations (to users with the proper permissions). +package = Administration +core = 7.x +files[] = admin_menu.module +files[] = admin_menu.inc +files[] = admin_menu.install + +; Information added by drupal.org packaging script on 2008-12-17 +version = "7.x-1.x-dev" +core = "7.x" +project = "admin_menu" +datestamp = "1229515229" + === added file 'sites/all/modules/admin_menu/admin_menu.install' --- sites/all/modules/admin_menu/admin_menu.install 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/admin_menu.install 2009-01-15 16:50:16 +0000 @@ -0,0 +1,44 @@ +condition('module', 'admin_menu')->execute(); + menu_cache_clear_all(); + // Delete variables. + variable_del('admin_menu_rebuild_links'); + variable_del('admin_menu_devel_modules_enabled'); + variable_del('admin_menu_margin_top'); + variable_del('admin_menu_tweak_modules'); +} + +/** + * Ensure that admin_menu is rebuilt after upgrading to D6. + */ +function admin_menu_update_6000() { + $ret = array(); + // Delete menu links. + db_delete('menu_links')->condition('module', 'admin_menu')->execute(); + variable_set('admin_menu_rebuild_links', TRUE); + // Drop the {admin_menu} table in admin_menu_update_6000() on sites that used + // one of the later patches in #132524. + if (db_table_exists('admin_menu')) { + $ret[] = update_sql("DROP TABLE {admin_menu}"); + } + return $ret; +} + +/** + * Wipe and rebuild so we can switch the icon path to . + */ +function admin_menu_update_6001() { + $ret = array(); + module_load_include('inc', 'admin_menu'); + admin_menu_wipe(); + $ret[] = array('success' => TRUE, 'query' => 'Administration menu links deleted for clean rebuild.'); + return $ret; +} + === added file 'sites/all/modules/admin_menu/admin_menu.js' --- sites/all/modules/admin_menu/admin_menu.js 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/admin_menu.js 2009-01-15 16:50:16 +0000 @@ -0,0 +1,50 @@ +/* $Id: admin_menu.js,v 1.13 2008/11/02 21:36:09 sun Exp $ */ + +$(document).ready(function() { + // Apply margin-top if enabled; directly applying marginTop doesn't work in IE. + if ($('#admin-menu').size()) { + if (Drupal.settings.admin_menu.margin_top) { + $('body').addClass('admin-menu'); + } + if (Drupal.settings.admin_menu.position_fixed) { + $('#admin-menu').css('position', 'fixed'); + } + // Move page tabs into administration menu. + if (Drupal.settings.admin_menu.tweak_tabs) { + $('ul.tabs.primary li').each(function() { + $(this).addClass('admin-menu-tab').appendTo('#admin-menu > ul'); + }); + $('ul.tabs.secondary').appendTo('#admin-menu > ul > li.admin-menu-tab.active'); + } + } + + // Collapse fieldsets on Modules page. For why multiple selectors see #111719. + if (Drupal.settings.admin_menu.tweak_modules) { + $('#system-modules fieldset:not(.collapsed), #system-modules-1 fieldset:not(.collapsed)').addClass('collapsed'); + } + + // Hover emulation for IE 6. + if ($.browser.msie && parseInt(jQuery.browser.version) == 6) { + $('#admin-menu li').hover(function() { + $(this).addClass('iehover'); + }, function() { + $(this).removeClass('iehover'); + }); + } + + // Delayed mouseout. + $('#admin-menu li').hover(function() { + // Stop the timer. + clearTimeout(this.sfTimer); + // Display child lists. + $('> ul', this).css({left: 'auto', display: 'block'}) + // Immediately hide nephew lists. + .parent().siblings('li').children('ul').css({left: '-999em', display: 'none'}); + }, function() { + // Start the timer. + var uls = $('> ul', this); + this.sfTimer = setTimeout(function() { + uls.css({left: '-999em', display: 'none'}); + }, 400); + }); +}); === added file 'sites/all/modules/admin_menu/admin_menu.module' --- sites/all/modules/admin_menu/admin_menu.module 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/admin_menu.module 2009-01-15 16:50:15 +0000 @@ -0,0 +1,325 @@ +' . t('The administration menu module provides a dropdown menu arranged for one- or two-click access to most administrative tasks and other common destinations (to users with the proper permissions). Administration menu also displays the number of anonymous and authenticated users, and allows modules to add their own custom menu items. Integration with the menu varies from module to module; the contributed module Devel, for instance, makes strong use of the administration menu module to provide quick access to development tools.', array('@drupal' => 'http://drupal.org/project/devel')) . '

'; + $output .= '

' . t('The administration menu settings page allows you to modify some elements of the menu\'s behavior and appearance. Since the appearance of the menu is dependent on your site theme, substantial customizations require modifications to your site\'s theme and CSS files. See the advanced module README.txt file for more information on theme and CSS customizations.', array('@settings' => url('admin/settings/admin_menu'))) . '

'; + $output .= '

' . t('The menu items displayed in the administration menu depend upon the actual permissions of the viewer. First, the administration menu is only displayed to users in roles with the Access administration menu (admin_menu module) permission. Second, a user must be a member of a role with the Access administration pages (system module) permission to view administrative links. And, third, only currently permitted links are displayed; for example, if a user is not a member of a role with the permissions Administer permissions (user module) and Administer users (user module), the User management menu item is not displayed.') . '

'; + return $output; + } +} + +/** + * Implementation of hook_perm(). + */ +function admin_menu_perm() { + return array( + 'access administration menu' => array( + 'title' => t('Access administration menu'), + 'description' => t('Display the administration menu at the top of each page.'), + ), + 'display drupal links' => array( + 'title' => t('Display Drupal links'), + 'description' => t('Provide Drupal.org links in the administration menu.'), + ) + ); +} + +/** + * Implementation of hook_theme(). + */ +function admin_menu_theme() { + return array( + 'admin_menu_item' => array( + 'arguments' => array('link' => '', 'has_children' => FALSE, 'menu' => '', 'in_active_trail' => FALSE, 'extra_class' => ''), + ), + 'admin_menu_icon' => array( + 'arguments' => array(), + ), + ); +} + +/** + * Implementation of hook_menu(). + */ +function admin_menu_menu() { + $items = array(); + $items['admin/settings/admin_menu'] = array( + 'title' => 'Administration menu', + 'description' => 'Adjust administration menu settings.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('admin_menu_theme_settings'), + 'access arguments' => array('administer site configuration'), + ); + $items['admin_menu/toggle-modules'] = array( + 'page callback' => 'admin_menu_toggle_modules', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK, + ); + $items['admin_menu/flush-cache'] = array( + 'page callback' => 'admin_menu_flush_cache', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Implementation of hook_init(). + * + * We can't move this into admin_menu_footer(), because PHP-only based themes + * like chameleon load and output scripts and stylesheets in front of + * theme_closure(), so we ensure Admin menu's styles and scripts are loaded on + * all pages via hook_init(). + */ +function admin_menu_init() { + if (user_access('access administration menu')) { + $path = drupal_get_path('module', 'admin_menu'); + drupal_add_css($path . '/admin_menu.css', array('preprocess' => FALSE)); + // Performance: Defer execution. + drupal_add_js($path . '/admin_menu.js', array('defer' => TRUE)); + + if ($setting = variable_get('admin_menu_margin_top', 1)) { + drupal_add_js(array('admin_menu' => array('margin_top' => $setting)), 'setting'); + } + if ($setting = variable_get('admin_menu_position_fixed', 0)) { + drupal_add_js(array('admin_menu' => array('position_fixed' => $setting)), 'setting'); + } + if ($setting = variable_get('admin_menu_tweak_tabs', 0)) { + drupal_add_js(array('admin_menu' => array('tweak_tabs' => $setting)), 'setting'); + } + if ($_GET['q'] == 'admin/build/modules') { + drupal_add_js(array('admin_menu' => array('tweak_modules' => variable_get('admin_menu_tweak_modules', 0))), 'setting'); + } + } +} + +/** + * Suppress display of administration menu. + * + * This function should be called from within another module's page callback + * (preferably using module_invoke()) when the menu should not be displayed. + * This is useful for modules that implement popup pages or other special + * pages where the menu would be distracting or break the layout. + * + * @param $set + * Defaults to TRUE. If called before hook_footer, the menu will not be + * displayed. Calling with FALSE returns the suppression state. + */ +function admin_menu_suppress($set = TRUE) { + static $suppress = FALSE; + if (!empty($set)) { + $suppress = TRUE; + } + return $suppress; +} + +/** + * Implementation of hook_footer(). + * + * Admin menu was previously output via hook_block(), but suffered from + * theme-specific stylesheets that may be applied to layout blocks. We now + * output Admin menu in the footer to circumvent this. + * + * @todo Since admin_menu is rebuilt in the same request, we should be able + * to use a helper function instead of a variable to remind us to rebuild + * (variable_set() is slow). + */ +function admin_menu_footer($main = 0) { + if (!user_access('access administration menu') || admin_menu_suppress(FALSE)) { + return; + } + + // Check for the flag indicating that we need to rebuild. + if (variable_get('admin_menu_rebuild_links', FALSE)) { + module_load_include('inc', 'admin_menu'); + _admin_menu_rebuild_links(); + variable_del('admin_menu_rebuild_links'); + } + + $content = '
'; + $content .= admin_menu_tree_output(menu_tree_all_data('admin_menu')); + $content .= '
'; + return $content; +} + +/** + * Returns a rendered menu tree. + * + * @param $tree + * A data structure representing the tree as returned from menu_tree_data. + * + * @return string + * The complete, rendered administration menu. + */ +function admin_menu_tree_output($tree) { + $output = ''; + + foreach ($tree as $data) { + $extra_class = isset($data['link']['localized_options']['extra class']) ? $data['link']['localized_options']['extra class'] : NULL; + $link = admin_menu_item_link($data['link']); + + if ($data['below']) { + $output .= theme('admin_menu_item', $link, $data['link']['has_children'], admin_menu_tree_output($data['below']), $data['link']['in_active_trail'], $extra_class); + } + else { + $output .= theme('admin_menu_item', $link, $data['link']['has_children'], '', $data['link']['in_active_trail'], $extra_class); + } + } + return $output ? "\n
    ". $output .'
' : ''; +} + +/** + * High-performance implementation of theme_menu_item_link(). + * + * This saves us a theme() call and does only the absolute minimum to get + * the admin menu links rendered. + */ +function admin_menu_item_link($link) { + // Omit alias lookups. + $link['localized_options']['alias'] = TRUE; + return ''. (!empty($link['localized_options']['html']) ? $link['title'] : check_plain($link['title'])) .''; +} + +/** + * Generate the HTML output for a single menu item and submenu. + * + * @param string $link + * A rendered menu item link. + * @param bool $has_children + * Whether this item has children. + * @param string $menu + * A string containing any rendered children of this item. + * @param bool $in_active_trail + * Whether this item is in the active menu trail. + * @param string $extra_class + * An additional CSS class to set for this item. + * + * @see theme_menu_item() + * @ingroup themeable + */ +function theme_admin_menu_item($link, $has_children, $menu = '', $in_active_trail = FALSE, $extra_class = NULL) { + $class = ($menu || $has_children ? 'expandable' : ''); + if (!empty($extra_class)) { + $class .= ' '. $extra_class; + } + if ($in_active_trail) { + $class .= ' active-trail'; + } + return ''. $link . $menu .''; +} + +/** + * Implementation of hook_form_[form_id]_alter(). + * + * Extends Devel module with Administration Menu developer settings. + */ +function admin_menu_form_devel_admin_settings_alter(&$form, $form_state) { + module_load_include('inc', 'admin_menu'); + _admin_menu_devel_settings_form_alter($form, $form_state); +} + +/** + * Implementation of hook_enable(). + */ +function admin_menu_enable() { + variable_set('admin_menu_rebuild_links', TRUE); +} + +/** + * Implementation of hook_menu_alter(). + */ +function admin_menu_menu_alter() { + variable_set('admin_menu_rebuild_links', TRUE); +} + +/** + * Implementation of hook_translated_menu_link_alter(). + * + * Here is where we make changes to links that need dynamic information such + * as the current page path or the number of users. + */ +function admin_menu_translated_menu_link_alter(&$item, $map) { + global $user, $base_url; + static $access_all; + + if (!isset($access_all)) { + // We only ever do this for development + $access_all = variable_get('admin_menu_show_all', 0) && module_exists('devel'); + } + if ($item['menu_name'] != 'admin_menu') { + return; + } + if ($access_all && !$item['access']) { + $item['access'] = TRUE; + // Prepare for http://drupal.org/node/266596 + if (!isset($item['localized_options'])) { + _menu_item_localize($item, $map, TRUE); + } + } + // Don't waste cycles altering items that are not visible + if (!$item['access']) { + return; + } + if ($item['link_path'] == $base_url . '/update.php' && $user->uid != 1) { + $item['access'] = FALSE; + return; + } + if ($item['link_path'] == 'http://drupal.org' && !user_access('display drupal links')) { + $item['access'] = FALSE; + return; + } + // Fix destination query strings + if (isset($item['localized_options']['query'])) { + if ($item['localized_options']['query'] == 'destination') { + $item['localized_options']['query'] = drupal_get_destination(); + } + } + if ($extra = variable_get('admin_menu_display', 0)) { + $item['title'] .= ' '. $extra[0] .': '. $item[$extra]; + } + // Handle items that need dynamic localization/replacement. + if (isset($item['options']['t'])) { + $item['title'] = t($item['title'], $item['options']['t'] + array('@username' => $GLOBALS['user']->name)); + } + if ($item['title'] == 'icon_users') { + // Add count of active anonymous/authenticated users. + // @see user_block(), user.module + $interval = REQUEST_TIME - variable_get('user_block_seconds_online', 900); + $count_anon = drupal_session_count($interval, TRUE); + $count_auth = drupal_session_count($interval, FALSE); + $icon_users = '@title'; + + $title = array('@title' => t('Current anonymous / authenticated users')); + $icon_users = strtr($icon_users, $title); + $item['title'] = t('@count-anon / @count-auth !icon', array('@count-anon' => $count_anon, '@count-auth' => $count_auth, '!icon' => $icon_users)); + if (user_access('administer users')) { + $item['href'] = 'admin/user/user'; + } + } +} + +/** + * Render an icon to display in the Administration Menu. + * + * @ingroup themeable + */ +function theme_admin_menu_icon() { + return ''. t('Home') .''; +} + === added directory 'sites/all/modules/admin_menu/images' === added file 'sites/all/modules/admin_menu/images/arrow-rtl.png' Binary files sites/all/modules/admin_menu/images/arrow-rtl.png 1970-01-01 00:00:00 +0000 and sites/all/modules/admin_menu/images/arrow-rtl.png 2009-01-15 16:50:16 +0000 differ === added file 'sites/all/modules/admin_menu/images/arrow.png' Binary files sites/all/modules/admin_menu/images/arrow.png 1970-01-01 00:00:00 +0000 and sites/all/modules/admin_menu/images/arrow.png 2009-01-15 16:50:15 +0000 differ === added file 'sites/all/modules/admin_menu/images/bkg.png' Binary files sites/all/modules/admin_menu/images/bkg.png 1970-01-01 00:00:00 +0000 and sites/all/modules/admin_menu/images/bkg.png 2009-01-15 16:50:16 +0000 differ === added file 'sites/all/modules/admin_menu/images/bkg_tab.png' Binary files sites/all/modules/admin_menu/images/bkg_tab.png 1970-01-01 00:00:00 +0000 and sites/all/modules/admin_menu/images/bkg_tab.png 2009-01-15 16:50:16 +0000 differ === added file 'sites/all/modules/admin_menu/images/icon_users.png' Binary files sites/all/modules/admin_menu/images/icon_users.png 1970-01-01 00:00:00 +0000 and sites/all/modules/admin_menu/images/icon_users.png 2009-01-15 16:50:16 +0000 differ === added directory 'sites/all/modules/admin_menu/tests' === added file 'sites/all/modules/admin_menu/tests/admin_menu.test.skip' --- sites/all/modules/admin_menu/tests/admin_menu.test.skip 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/tests/admin_menu.test.skip 2009-01-15 16:50:15 +0000 @@ -0,0 +1,52 @@ + t('Admin menu functionality'), + 'description' => t('Enable a module, make sure an admin user can see the new link.'), + 'group' => t('Admin menu tests'), + ); + } + + function setUp() { + parent::setUp('admin_menu'); + } + + /** + * Test that the links are added to the page (no JS testing). + */ + function testAdminMenu() { + // Anonymous users should not see the menu. + $this->assertNoRaw('
', 'Admin menu not displayed to anonymous.'); + + // Create a user who can see the admin_menu links, but without the + // permission 'display drupal links'. + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'administer nodes', 'bypass node access', 'access administration menu', 'administer site-wide contact form')); + $this->drupalLogin($admin_user); + + // Check that the user can see the admin links, but not the drupal links. + $this->assertRaw('
', 'Admin menu displayed to admin user.'); + $this->drupalGet('node'); + $this->assertTrue(preg_match('@
(.*\n)*.*admin/content/node@', $this->drupalGetContent()), 'Administer content link found'); + $this->assertFalse(preg_match('@
(.*\n)*.*http://drupal.org@', $this->drupalGetContent()), 'Drupal link not found'); + $this->assertFalse(preg_match('@
(.*\n)*.*admin/build/contact@', $this->drupalGetContent()), 'Contact module link not found'); + + // Check that a link for the newly enabled module appears. + $this->drupalModuleEnable('contact'); + $this->drupalGet('node'); + $this->assertTrue(preg_match('@
(.*\n)*.*admin/build/contact@', $this->drupalGetContent()), 'Contact module link found'); + $this->drupalLogout(); + + // A second user with the added permission 'display drupal links', but not + // 'administer site-wide contact form'. + $admin_user2 = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'administer nodes', 'access administration menu', 'display drupal links')); + $this->drupalLogin($admin_user2); + $this->drupalGet('node'); + $this->assertTrue(preg_match('@
(.*\n)*.*http://drupal.org@', $this->drupalGetContent()), 'Drupal link found'); + $this->assertFalse(preg_match('@
(.*\n)*.*admin/build/contact@', $this->drupalGetContent()), 'Contact module link not found'); + } +} + === added directory 'sites/all/modules/admin_menu/translations' === added file 'sites/all/modules/admin_menu/translations/admin_menu.pot' --- sites/all/modules/admin_menu/translations/admin_menu.pot 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/translations/admin_menu.pot 2009-01-15 16:50:16 +0000 @@ -0,0 +1,202 @@ +# $Id: admin_menu.pot,v 1.2 2008/12/02 01:19:29 sun Exp $ +# +# LANGUAGE translation of Drupal (general) +# Copyright YEAR NAME +# Generated from files: +# admin_menu.inc,v 1.33 2008/12/02 00:45:56 sun +# admin_menu.module,v 1.60 2008/11/30 12:50:23 sun +# admin_menu.info,v 1.9 2008/11/30 12:50:23 sun +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2008-12-02 02:18+0100\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" + +#: admin_menu.inc:261 +msgid "Enable developer modules" +msgstr "" + +#: admin_menu.inc:261 +msgid "Disable developer modules" +msgstr "" + +#: admin_menu.inc:346 +msgid "Adjust top margin" +msgstr "" + +#: admin_menu.inc:348 +msgid "If enabled, the site output is shifted down approximately 20 pixels from the top of the viewport to display the administration menu. If disabled, some absolute- or fixed-positioned page elements may be covered by the administration menu." +msgstr "" + +#: admin_menu.inc:352 +msgid "Keep menu at top of page" +msgstr "" + +#: admin_menu.inc:354 +msgid "If enabled, the administration menu is always displayed at the top of the browser viewport (even after the page is scrolled). Note: In some browsers, this setting results in a malformed page, an invisible cursor, non-selectable elements in forms, or other issues. Disable this option if these issues occur." +msgstr "" + +#: admin_menu.inc:358 +msgid "Advanced settings" +msgstr "" + +#: admin_menu.inc:362 +msgid "Collapse fieldsets on modules page" +msgstr "" + +#: admin_menu.inc:364 +msgid "If enabled, fieldsets on the modules page are automatically collapsed when loading the page." +msgstr "" + +#: admin_menu.inc:367 +msgid "If the Utility module was installed for this purpose, it can be safely disabled and uninstalled." +msgstr "" + +#: admin_menu.inc:371 +msgid "Move local tasks into menu" +msgstr "" + +#: admin_menu.inc:373 +msgid "If enabled, the tabs on the current page are moved into the administration menu. This feature is only available in themes that use the CSS classes tabs primary and tabs secondary for tabs." +msgstr "" + +#: admin_menu.inc:378 +msgid "If the administration menu displays duplicate menu items, you may need to rebuild your menu items using the Wipe and rebuild button." +msgstr "" + +#: admin_menu.inc:382 +msgid "Wipe and rebuild" +msgstr "" + +#: admin_menu.inc:412 +msgid "Administration menu settings" +msgstr "" + +#: admin_menu.inc:417 +msgid "None" +msgstr "" + +#: admin_menu.inc:417 +msgid "Menu link ID" +msgstr "" + +#: admin_menu.inc:417 +msgid "Weight" +msgstr "" + +#: admin_menu.inc:417 +msgid "Parent link ID" +msgstr "" + +#: admin_menu.inc:420 +msgid "Display additional data for each menu item" +msgstr "" + +#: admin_menu.inc:423 +msgid "Display the selected items next to each menu item link." +msgstr "" + +#: admin_menu.inc:427 +msgid "Display all menu items" +msgstr "" + +#: admin_menu.inc:429 +msgid "If enabled, all menu items are displayed regardless of your site permissions. Note: Do not enable on a production site." +msgstr "" + +#: admin_menu.inc:446 +msgid "Enabled these modules: !module-list." +msgstr "" + +#: admin_menu.inc:458 +msgid "Disabled these modules: !module-list." +msgstr "" + +#: admin_menu.inc:462 +msgid "No developer modules are enabled." +msgstr "" + +#: admin_menu.module:16 +msgid "The administration menu module provides a dropdown menu arranged for one- or two-click access to most administrative tasks and other common destinations (to users with the proper permissions). Use the settings below to customize the appearance of the menu." +msgstr "" + +#: admin_menu.module:20 +msgid "The administration menu module provides a dropdown menu arranged for one- or two-click access to most administrative tasks and other common destinations (to users with the proper permissions). Administration menu also displays the number of anonymous and authenticated users, and allows modules to add their own custom menu items. Integration with the menu varies from module to module; the contributed module Devel, for instance, makes strong use of the administration menu module to provide quick access to development tools." +msgstr "" + +#: admin_menu.module:21 +msgid "The administration menu settings page allows you to modify some elements of the menu's behavior and appearance. Since the appearance of the menu is dependent on your site theme, substantial customizations require modifications to your site's theme and CSS files. See the advanced module README.txt file for more information on theme and CSS customizations." +msgstr "" + +#: admin_menu.module:22 +msgid "The menu items displayed in the administration menu depend upon the actual permissions of the viewer. First, the administration menu is only displayed to users in roles with the Access administration menu (admin_menu module) permission. Second, a user must be a member of a role with the Access administration pages (system module) permission to view administrative links. And, third, only currently permitted links are displayed; for example, if a user is not a member of a role with the permissions Administer permissions (user module) and Administer users (user module), the User management menu item is not displayed." +msgstr "" + +#: admin_menu.module:33;33 +msgid "Access administration menu" +msgstr "" + +#: admin_menu.module:34;34 +msgid "Display the administration menu at the top of each page." +msgstr "" + +#: admin_menu.module:37;37 +msgid "Display Drupal links" +msgstr "" + +#: admin_menu.module:38;38 +msgid "Provide Drupal.org links in the administration menu." +msgstr "" + +#: admin_menu.module:308 +msgid "Current anonymous / authenticated users" +msgstr "" + +#: admin_menu.module:310 +msgid "@count-anon / @count-auth !icon" +msgstr "" + +#: admin_menu.module:32 +msgid "access administration menu" +msgstr "" + +#: admin_menu.module:33;37 +msgid "title" +msgstr "" + +#: admin_menu.module:34;38 +msgid "description" +msgstr "" + +#: admin_menu.module:36 +msgid "display drupal links" +msgstr "" + +#: admin_menu.module:63 admin_menu.info:0 +msgid "Administration menu" +msgstr "" + +#: admin_menu.module:64 +msgid "Adjust administration menu settings." +msgstr "" + +#: admin_menu.module:0 +msgid "admin_menu" +msgstr "" + +#: admin_menu.info:0 +msgid "Provides a dropdown menu to most administrative tasks and other common destinations (to users with the proper permissions)." +msgstr "" + +#: admin_menu.info:0 +msgid "Administration" +msgstr "" + === added file 'sites/all/modules/admin_menu/translations/cs.po' --- sites/all/modules/admin_menu/translations/cs.po 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/translations/cs.po 2009-01-15 16:50:16 +0000 @@ -0,0 +1,188 @@ +# Czech translation of admin_menu (6.x-1.1) +# Copyright (c) 2008 by Jakub Suchy +# Generated from files: +# admin_menu.inc,v 1.11.2.10 2008/09/12 16:21:22 pwolanin +# admin_menu.info,v 1.5 2008/04/26 16:10:09 sun +# admin_menu.module,v 1.43.2.7 2008/09/09 14:39:01 sun +# admin_menu.install,v 1.4.2.4 2008/09/12 16:21:22 pwolanin +# +msgid "" +msgstr "" +"Project-Id-Version: admin_menu (6.x-1.1)\n" +"POT-Creation-Date: 2008-11-15 15:00+0100\n" +"PO-Revision-Date: 2008-11-01 18:01+0100\n" +"Language-Team: Czech\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=((n==1)?(0):(((n>=2)&&(n<=4))?(1):2));\n" + +#: admin_menu.inc:395 +msgid "Weight" +msgstr "Váha" + +#: admin_menu.info:0 +msgid "Administration" +msgstr "Administrace" + +#: admin_menu.module:34 +msgid "User management" +msgstr "Uživatelé" + +#: admin_menu.inc:395 +msgid "None" +msgstr "Žádná" + +#: admin_menu.inc:252 +msgid "Re-enable developer modules" +msgstr "Povolit vývojářské moduly" + +#: admin_menu.inc:252 +msgid "Disable developer modules" +msgstr "Zakázat vývojářské moduly" + +#: admin_menu.inc:338; admin_menu.module:34 +msgid "Apply margin-top to page body" +msgstr "Aplikovat margin-top na tělo stránky" + +#: admin_menu.inc:340 +msgid "" +"If enabled, the output of this site will be shifted for approx. 20 " +"pixels from the top of the viewport to make room for the " +"Administration Menu. If this setting is disabled, some absolute or " +"fixed positioned page elements at the top of the viewport may be " +"covered by Administration Menu." +msgstr "" +"Pokud povoleno, zobrazení tohoto webu bude pro zobrazení Admin menu " +"posunuto o cca 20 pixelů níže. Pokud vypnuto, některé absolutně " +"pozicované prvky tématu vzhledu mohou být administračním menu " +"překryty." + +#: admin_menu.inc:344 +msgid "Drupal Administration Tweaks" +msgstr "Další nastavení administrace" + +#: admin_menu.inc:348 +msgid "Collapse fieldsets on modules page" +msgstr "Srolovat pole na stránce modulů" + +#: admin_menu.inc:350 +msgid "" +"If enabled, fieldsets on the Site building " +"» Modules page will be initially collapsed." +msgstr "" +"Pokud povoleno, rolovací pole na Prvky webu " +"» Moduly budou ve výchozím zobrazení srolovány." + +#: admin_menu.inc:353 +msgid "" +"If you installed Utility module for this purpose, you can safely " +"uninstall it now!" +msgstr "" +"Pokud jste pro tuto funkcionalitu nainstalovali modul Utility, " +"můžete ho nyní vypnout!" + +#: admin_menu.inc:358 +msgid "" +"If you are seeing a duplicated menu or other problems, you may use " +"this button to delete everything in the menu and rebuild it." +msgstr "" +"Pokud máte problém s duplicitním menu či jiné problémy, je " +"možné použít toto tlačítko pro smazání a regenerování menu." + +#: admin_menu.inc:362 +msgid "Wipe and rebuild" +msgstr "Smazat a regenerovat" + +#: admin_menu.inc:390 +msgid "Administration Menu settings" +msgstr "Nastavení Administračního menu" + +#: admin_menu.inc:395 +msgid "Menu link id" +msgstr "Id menu odkazu" + +#: admin_menu.inc:395 +msgid "Parent link id" +msgstr "Id nadřazeného odkazu" + +#: admin_menu.inc:398 +msgid "Display extra information for menu items in Drupal Administration Menu" +msgstr "Zobrazit extra informace pro položky v Administračním menu" + +#: admin_menu.inc:401 +msgid "" +"If enabled, the chosen information will appear next to each menu item " +"link." +msgstr "" +"Pokud povoleno, vybraná informace se zobrazí u každého odkazu v " +"menu." + +#: admin_menu.inc:405 +msgid "Display all menu items" +msgstr "Zobrazit všechny položky menu" + +#: admin_menu.inc:407 +msgid "" +"Enable this option to disable user access checks for menu items, i.e. " +"every menu item in the visible menu tree will be displayed to every " +"user regardless of access permissions." +msgstr "" +"Povolte tuto možnost pro vypnutí kontroly práv přístupu pro " +"položky menu, tzn. každá položka menu bude viditelná všem " +"uživatelům." + +#: admin_menu.inc:424 +msgid "Re-enabled these modules: !module-list." +msgstr "Znovupovoleny tyto moduly: !module-list." + +#: admin_menu.inc:436 +msgid "Disabled these modules: !module-list." +msgstr "Zakázány tyto moduly: !module-list." + +#: admin_menu.inc:440 +msgid "No developer modules are enabled." +msgstr "Žádné vývojářské moduly nejsou povoleny." + +#: admin_menu.module:16 +msgid "" +"The Administration Menu will appear at the top of the page for users " +"who have been given the \"access administration menu\" permission. " +"Customize appearance of the dropdown menu here." +msgstr "" +"Administrační menu se zobrazí nahoře ve stránce všem " +"uživatelům, kteří mají právo \"zobrazit administrační menu\". " +"Zobrazení menu se dá nastavit zde." + +#: admin_menu.module:284 +msgid "Current anonymous / authenticated users" +msgstr "Současní anonymní / přihlášení uživatelé" + +#: admin_menu.module:286 +msgid "@count-anon / @count-auth !icon" +msgstr "@count-anon / @count-auth !icon" + +#: admin_menu.module:42 +msgid "access administration menu" +msgstr "zobrazit administrační menu" + +#: admin_menu.module:42 +msgid "display drupal links" +msgstr "zobrazit drupal odkazy" + +#: admin_menu.module:65; admin_menu.info:0 +msgid "Administration Menu" +msgstr "Administrační menu" + +#: admin_menu.module:66 +msgid "Adjust settings for the dropdown Administration Menu." +msgstr "Nastavení pro Administrační menu" + +#: admin_menu.module:0 +msgid "admin_menu" +msgstr "admin_menu" + +#: admin_menu.install:41 +msgid "admin_menu links deleted for clean rebuild." +msgstr "admin_menu odkazy smazány" + === added file 'sites/all/modules/admin_menu/translations/da.po' --- sites/all/modules/admin_menu/translations/da.po 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/translations/da.po 2009-01-15 16:50:15 +0000 @@ -0,0 +1,146 @@ +# $Id: da.po,v 1.1 2008/04/26 16:10:10 sun Exp $ +# +# Danish translation of Drupal admin_menu module +# Copyright 2008 Mikkel Høgh +# Generated from files: +# admin_menu.inc,v 1.1.4.10 2008/02/24 03:56:22 sun +# admin_menu.module,v 1.26.2.16 2008/02/24 03:56:22 sun +# admin_menu.info,v 1.3.6.1 2007/12/02 06:25:51 sun +# +msgid "" +msgstr "" +"Project-Id-Version: admin_menu 5.x-2.4\n" +"POT-Creation-Date: 2008-03-26 15:11+0100\n" +"PO-Revision-Date: 2008-03-26 17:33+0100\n" +"Last-Translator: Mikkel Høgh \n" +"Language-Team: Danish \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" + +#: admin_menu.inc:133 +msgid "Administer" +msgstr "Administration" + +#: admin_menu.inc:150 +msgid "Run cron" +msgstr "Kør cron" + +#: admin_menu.inc:152 +msgid "Run updates" +msgstr "Kør databaseopdatering" + +#: admin_menu.inc:157 +msgid "Drupal.org" +msgstr "Drupal.org" + +#: admin_menu.inc:158 +msgid "Drupal issue queue" +msgstr "Drupal issue-kø" + +#: admin_menu.inc:170 +msgid "@title issue queue" +msgstr "@title issue-kø" + +#: admin_menu.inc:178 +msgid "Logout @name" +msgstr "Log @name ud" + +#: admin_menu.inc:200 +msgid "Add !title" +msgstr "Tilføj !title" + +#: admin_menu.module:20 +msgid "Renders the administer menu tree as dropdown menu at the top of the window." +msgstr "Viser administrationsmenuen som en dropdown-menu øverst på siden." + +#: admin_menu.module:22 +msgid "The Administration Menu will appear at the top of the page for users who have been given the \"access administration menu\" permission. Customize appearance of the dropdown menu here." +msgstr "Administrationsmenuen vil blive vist øverst på siden for brugere der har tilladelsen \"tilgå administrationsmenu\". Du kan indstille dens udseende her." + +#: admin_menu.module:39;85 +msgid "Apply margin-top to page body" +msgstr "Læg margin-top på sidens body-tag" + +#: admin_menu.module:63 +#: admin_menu.info:0 +msgid "Administration Menu" +msgstr "Administrationsmenu" + +#: admin_menu.module:64 +msgid "Adjust settings for the dropdown Administration Menu." +msgstr "Indstil drop-down-adminstrationsmenuen." + +#: admin_menu.module:87 +msgid "If this setting is disabled, some absolute or fixed positioned page elements may be covered by Administration Menu." +msgstr "Hvis denne indstilling er slået fra, vil nogen absolute eller fixed positionerede elementer på siden muligvis blive dækket af administrationsmenuen." + +#: admin_menu.module:319 +msgid "Current anonymous / authenticated users" +msgstr "Nuværende antal af bruger der ikke er / er logget ind" + +#: admin_menu.module:343 +msgid "Administration Menu settings" +msgstr "Administrationsmenu-indstillinger" + +#: admin_menu.module:348 +msgid "None" +msgstr "Ingen" + +#: admin_menu.module:348 +msgid "Id" +msgstr "Id" + +#: admin_menu.module:348 +msgid "Weight" +msgstr "Vægt" + +#: admin_menu.module:348 +msgid "Parent id" +msgstr "Parent id" + +#: admin_menu.module:351 +msgid "Display extra information for menu items in Drupal Administration Menu" +msgstr "Vis ekstra information for menuelementer i Drupal-administrationsmenuen" + +#: admin_menu.module:354 +msgid "If enabled, the chosen information will appear next to each menu item link." +msgstr "Hvis denne indstilling er slået til, vil den valgte information dukke op ved siden af hvert menuelement." + +#: admin_menu.module:358 +msgid "Display all menu items" +msgstr "Vis alle menuelementer" + +#: admin_menu.module:360 +msgid "Enable this option to disable user access checks for menu items, i.e. every menu item in the visible menu tree will be displayed to every user regardless of access permissions." +msgstr "Slå denne indstilling til for at slå adgangskontrol fra for elementerne i administrationsmenuen. Altså vil alle menuelementer blive vist til alle bruger, uanset om de har adgang til dem eller ej." + +#: admin_menu.module:384 +msgid "Variable editor" +msgstr "Variable-redigering" + +#: admin_menu.module:390 +msgid "Empty cache" +msgstr "Tøm cache" + +#: admin_menu.module:47 +msgid "access administration menu" +msgstr "tilgå administrationsmenu" + +#: admin_menu.module:47 +msgid "display drupal links" +msgstr "vis Drupal-links" + +#: admin_menu.module:0 +msgid "admin_menu" +msgstr "admin_menu" + +#: admin_menu.info:0 +msgid "Renders a menu tree for administrative purposes as dropdown menu at the top of the window." +msgstr "Viser en menu til administrationsformål øverst på siden." + +#: admin_menu.info:0 +msgid "Administration" +msgstr "Administration" + === added file 'sites/all/modules/admin_menu/translations/de.po' --- sites/all/modules/admin_menu/translations/de.po 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/translations/de.po 2009-01-15 16:50:15 +0000 @@ -0,0 +1,112 @@ +msgid "" +msgstr "" +"Project-Id-Version: admin_menu v1.x\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2007-12-02 07:59+0100\n" +"Last-Translator: unleashed mind \n" +"Language-Team: unleashed mind \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" + +#: admin_menu.inc:126 +msgid "Administer" +msgstr "Verwalten" + +#: admin_menu.inc:143 +msgid "Run cron" +msgstr "Cron ausführen" + +#: admin_menu.inc:145 +msgid "Run updates" +msgstr "Aktualisierungen durchführen" + +#: admin_menu.inc:150 +msgid "Drupal.org" +msgstr "Drupal.org" + +#: admin_menu.inc:151 +msgid "Drupal issue queue" +msgstr "Drupal Themen" + +#: admin_menu.inc:162 +msgid "@title issue queue" +msgstr "@title Themen" + +#: admin_menu.inc:170 +msgid "Logout @name" +msgstr "@name abmelden" + +#: admin_menu.inc:180 +msgid "Add !title" +msgstr "!title erstellen" + +#: admin_menu.module:20 +msgid "Renders the administer menu tree as dropdown menu at the top of the window." +msgstr "Stellt den Menübaum unter „Verwalten“ in einem ausklappbaren Menü am Seitenanfang dar." + +#: admin_menu.module:299 +msgid "Current anonymous / authenticated users" +msgstr "Aktuelle anonyme / authentifizierte Benutzer" + +#: admin_menu.module:322 +msgid "Administration Menu settings" +msgstr "Administrationsmenü Einstellungen" + +#: admin_menu.module:327 +msgid "None" +msgstr "Nichts" + +#: admin_menu.module:327 +#, fuzzy +msgid "Id" +msgstr "ID" + +#: admin_menu.module:327 +msgid "Weight" +msgstr "Reihung" + +#: admin_menu.module:327 +msgid "Parent id" +msgstr "Übergeordnete ID" + +#: admin_menu.module:330 +msgid "Display extra information for menu items in Drupal Administration Menu" +msgstr "Zusatzinformationen für Menüpunkte im Administrationsmenü anzeigen" + +#: admin_menu.module:333 +msgid "If enabled, the chosen information will appear next to each menu item link." +msgstr "Falls aktiviert, wird die ausgewählte Information neben jedem Menüpunkt angezeigt." + +#: admin_menu.module:337 +msgid "Display all menu items" +msgstr "Alle Menüpunkte anzeigen" + +#: admin_menu.module:339 +msgid "Enable this option to disable user access checks for menu items, i.e. every menu item in the visible menu tree will be displayed to every user regardless of access permissions." +msgstr "Falls aktiviert, werden die Zugriffsberechtigungen für Menüpunkte nicht überprüft, d.h. jedem Benutzer werden alle Menüpunkte im Menübaum 'visible' angezeigt, unabhängig von den Zugriffsberechtigungen." + +#: admin_menu.module:365 +msgid "Variable editor" +msgstr "Variableneditor" + +#: admin_menu.module:371 +msgid "Empty cache" +msgstr "Cache leeren" + +#: admin_menu.info:0 +msgid "Administration Menu" +msgstr "Administrationsmenü" + +#: admin_menu.info:0 +msgid "Renders a menu tree for administrative purposes as dropdown menu at the top of the window." +msgstr "Stellt einen Menübaum für administrative Zwecke in einem ausklappbaren Menü am Seitenanfang dar." + +#: admin_menu.info:0 +msgid "Administration" +msgstr "Administration" + === added file 'sites/all/modules/admin_menu/translations/es.po' --- sites/all/modules/admin_menu/translations/es.po 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/translations/es.po 2009-01-15 16:50:16 +0000 @@ -0,0 +1,112 @@ +msgid "" +msgstr "" +"Project-Id-Version: admin_menu v1.x\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2008-10-20 11:18-0500\n" +"Last-Translator: Dimar Anez \n" +"Language-Team: Dimar Anez \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: Spanish\n" +"X-Poedit-Country: UNITED STATES\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: admin_menu.inc:126 +msgid "Administer" +msgstr "Administrar" + +#: admin_menu.inc:143 +msgid "Run cron" +msgstr "Correr automatizador de tareas" + +#: admin_menu.inc:145 +msgid "Run updates" +msgstr "Actualizar" + +#: admin_menu.inc:150 +msgid "Drupal.org" +msgstr "Drupal.org" + +#: admin_menu.inc:151 +msgid "Drupal issue queue" +msgstr "Drupal issue query" + +#: admin_menu.inc:162 +msgid "@title issue queue" +msgstr "@title issue query" + +#: admin_menu.inc:170 +msgid "Logout @name" +msgstr "Logout @name" + +#: admin_menu.inc:180 +msgid "Add !title" +msgstr "Adicionar !title" + +#: admin_menu.module:20 +msgid "Renders the administer menu tree as dropdown menu at the top of the window." +msgstr "Transforma el menu de administración de un arbol a un dropdown en la parte superior de la ventana." + +#: admin_menu.module:299 +msgid "Current anonymous / authenticated users" +msgstr "Usuarios anónimos / autenticados" + +#: admin_menu.module:322 +msgid "Administration Menu settings" +msgstr "Configuración del Menu de Administración" + +#: admin_menu.module:327 +msgid "None" +msgstr "Ninguno" + +#: admin_menu.module:327 +#, fuzzy +msgid "Id" +msgstr "ID" + +#: admin_menu.module:327 +msgid "Weight" +msgstr "Peso" + +#: admin_menu.module:327 +msgid "Parent id" +msgstr "ID Padre" + +#: admin_menu.module:330 +msgid "Display extra information for menu items in Drupal Administration Menu" +msgstr "Muestra información adicional de los elementos del menu en el Menu de Administración de Drupal" + +#: admin_menu.module:333 +msgid "If enabled, the chosen information will appear next to each menu item link." +msgstr "Si se habilita, la información seleccionada aparecerá al lado de cada enlace de elemento del menu." + +#: admin_menu.module:337 +msgid "Display all menu items" +msgstr "Mostrar todos los elementos del menu" + +#: admin_menu.module:339 +msgid "Enable this option to disable user access checks for menu items, i.e. every menu item in the visible menu tree will be displayed to every user regardless of access permissions." +msgstr "Seleccione esta opción para desabilitar chequeo de acceso de los usuarios a los elementos del menu. Ej: cada elemento del arbol del menú será mostrado a todos los usuarios sin importar sus permisos de accesos." + +#: admin_menu.module:365 +msgid "Variable editor" +msgstr "Editor de Variables" + +#: admin_menu.module:371 +msgid "Empty cache" +msgstr "Vaciar la caché" + +#: admin_menu.info:0 +msgid "Administration Menu" +msgstr "Menu de Administración" + +#: admin_menu.info:0 +msgid "Renders a menu tree for administrative purposes as dropdown menu at the top of the window." +msgstr "Muestra un arbol del menu para propósitos administrativos como un menu dropdown en la parte superior de la ventana." + +#: admin_menu.info:0 +msgid "Administration" +msgstr "Administración" + === added file 'sites/all/modules/admin_menu/translations/hu.po' --- sites/all/modules/admin_menu/translations/hu.po 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/translations/hu.po 2009-01-15 16:50:16 +0000 @@ -0,0 +1,64 @@ +# translation of admin_menu-module.hu.po to Hungarian +# Hungarian translation of Drupal (admin_menu.module) +# Generated from files: +# admin_menu.module,v 1.10.4.1 2007/03/31 14:20:05 sun +# admin_menu.info,v 1.3 2007/02/10 18:18:34 sun +# +# Copyright FEHÉR János , 2007. +msgid "" +msgstr "" +"Project-Id-Version: admin_menu-module.hu\n" +"POT-Creation-Date: 2007-05-09 19:04+0200\n" +"PO-Revision-Date: 2007-05-09 19:15+0200\n" +"Last-Translator: FEHÉR János \n" +"Language-Team: Hungarian >\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-Generator: KBabel 1.11.4\n" + +#: admin_menu.module:18 +msgid "Admin Menu Block" +msgstr "Admin menü blokk" + +#: admin_menu.module:161 +msgid "Add !content-type" +msgstr "!content-type hozzáadása" + +#: admin_menu.module:291 +msgid "Administration Menu settings" +msgstr "Adminisztrációs menü beállításai" + +#: admin_menu.module:297 +msgid "Display menu item IDs in Drupal Administration Menu" +msgstr "Megjeleníti a menüpont ID-ket a Drupal Adminisztrációs menüjében" + +#: admin_menu.module:299 +msgid "If enabled, the corresponding menu item id will appear next to each menu item link." +msgstr "Segítségével az adott menüpont ID-je megjelenik menüpont linkje mellett." + +#: admin_menu.module:303 +msgid "Display all menu items" +msgstr "Összes menüpont megjelenítése" + +#: admin_menu.module:305 +msgid "Enable this option to disable user access checks for menu items, i.e. every menu item in the visible menu tree will be displayed to every user regardless of access permissions." +msgstr "Ezzel a beállítással minden felhasználónak megjelenik az összes menüpont, függetlenül attól, hogy van-e jogosultsága használni vagy sem." + +#: admin_menu.module:0 +msgid "admin_menu" +msgstr "admin_menu" + +#: admin_menu.info:0 +msgid "Administration Menu" +msgstr "Adminisztrációs menü" + +#: admin_menu.info:0 +msgid "Administration" +msgstr "Adminisztráció" + +#: admin_menu.module:7 admin_menu.info:0 +msgid "Renders the administer menu tree as dropdown menu at the top of the window." +msgstr "A böngészőablak tetején jeleníti meg az adminisztrációs menüt" + === added file 'sites/all/modules/admin_menu/translations/ja.po' --- sites/all/modules/admin_menu/translations/ja.po 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/translations/ja.po 2009-01-15 16:50:16 +0000 @@ -0,0 +1,55 @@ +# $Id: ja.po,v 1.1 2008/04/26 16:10:10 sun Exp $ +# ----------------------------------------------------------------------------- +# Japanese translation of Drupal (admin_menu.module) +# +# Copyright (c) 2006-2007 Drupal Japan ( http://drupal.jp/ ) / +# Drupal Nippon ( http://drupon.org/ ) / +# Takafumi ( jp.drupal@imagine **reverse order**) +# +# Generated from file: +# admin_menu.module,v 1.10.4.1 2007/03/31 14:20:05 sun +# admin_menu.info,v 1.3 2007/02/10 18:18:34 sun +# +# ----------------------------------------------------------------------------- +msgid "" +msgstr "" +"POT-Creation-Date: 2007-04-05 01:17+0900\n" +"Last-Translator: Takafumi \n" +"Language-Team: Drupal Japan / Drupal Nippon\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" + +#: admin_menu.module:18 +msgid "Admin Menu Block" +msgstr "管理メニューブロック" + +#: admin_menu.module:161 +msgid "Add !content-type" +msgstr "!content-typeの追加" + +#: admin_menu.module:291 +msgid "Administration Menu settings" +msgstr "管理メニューの設定" + +#: admin_menu.module:297 +msgid "Display menu item IDs in Drupal Administration Menu" +msgstr "メニューアイテムIDを管理メニューに表示" + +#: admin_menu.module:299 +msgid "If enabled, the corresponding menu item id will appear next to each menu item link." +msgstr "有効にすると、対応するメニューIDが各メニューアイテムリンクの隣に表示されます。" + +#: admin_menu.module:303 +msgid "Display all menu items" +msgstr "すべてのメニューアイテムを表示" + +#: admin_menu.module:305 +msgid "Enable this option to disable user access checks for menu items, i.e. every menu item in the visible menu tree will be displayed to every user regardless of access permissions." +msgstr "このオプションを有効にすると、メニューアイテムのユーザアクセスチェックが無効になります。 すなわち、有効となっているすべてのメニューツリーのメニューアイテムが、アクセス権限の有無に関わらず、すべてのユーザに表示されることになります。" + +#: admin_menu.module:7 admin_menu.info:0 +msgid "Renders the administer menu tree as dropdown menu at the top of the window." +msgstr "ウィンドウ最上部にドロップダウン型の管理メニューツリーを表示します。" + === added file 'sites/all/modules/admin_menu/translations/nl.po' --- sites/all/modules/admin_menu/translations/nl.po 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/translations/nl.po 2009-01-15 16:50:16 +0000 @@ -0,0 +1,110 @@ +msgid "" +msgstr "" +"Project-Id-Version: admin_menu v1.x\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2008-10-10 05:53+0100\n" +"Last-Translator: Behets Tom \n" +"Language-Team: betz \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: Dutch\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: admin_menu.inc:126 +msgid "Administer" +msgstr "Beheer" + +#: admin_menu.inc:143 +msgid "Run cron" +msgstr "Voer cron uit" + +#: admin_menu.inc:145 +msgid "Run updates" +msgstr "Voer updates uit" + +#: admin_menu.inc:150 +msgid "Drupal.org" +msgstr "Drupal.org" + +#: admin_menu.inc:151 +msgid "Drupal issue queue" +msgstr "Drupal issue queue" + +#: admin_menu.inc:162 +msgid "@title issue queue" +msgstr "@title issue queue" + +#: admin_menu.inc:170 +msgid "Logout @name" +msgstr "@name afmelden" + +#: admin_menu.inc:180 +msgid "Add !title" +msgstr "Voe !title toe" + +#: admin_menu.module:20 +msgid "Renders the administer menu tree as dropdown menu at the top of the window." +msgstr "Rendert de administer menu als een dropdown menu bovenaan de pagina." + +#: admin_menu.module:299 +msgid "Current anonymous / authenticated users" +msgstr "Momentele anonieme / ingelogde gebruikers." + +#: admin_menu.module:322 +msgid "Administration Menu settings" +msgstr "Administration Menu instellingen" + +#: admin_menu.module:327 +msgid "None" +msgstr "Geen" + +#: admin_menu.module:327 +msgid "Id" +msgstr "Id" + +#: admin_menu.module:327 +msgid "Weight" +msgstr "Gewicht" + +#: admin_menu.module:327 +msgid "Parent id" +msgstr "Voorgaande id" + +#: admin_menu.module:330 +msgid "Display extra information for menu items in Drupal Administration Menu" +msgstr "Geef extra informatie weer over menu items in de Drupal Administration Menu" + +#: admin_menu.module:333 +msgid "If enabled, the chosen information will appear next to each menu item link." +msgstr "Indien enabled zal de gekozen informatie naast elke menu item tevoorschijn komen." + +#: admin_menu.module:337 +msgid "Display all menu items" +msgstr "Geef alle menu items weer" + +#: admin_menu.module:339 +msgid "Enable this option to disable user access checks for menu items, i.e. every menu item in the visible menu tree will be displayed to every user regardless of access permissions." +msgstr "Enable deze optie om user access checks te disablen voor menu items. Elk menu item zal hierdoor zichtbaar worden zonder rekening te houden met de gebruiker z'n permissies." + +#: admin_menu.module:365 +msgid "Variable editor" +msgstr "Variabel editor" + +#: admin_menu.module:371 +msgid "Empty cache" +msgstr "Cache leegmaken" + +#: admin_menu.info:0 +msgid "Administration Menu" +msgstr "Administration Menu" + +#: admin_menu.info:0 +msgid "Renders a menu tree for administrative purposes as dropdown menu at the top of the window." +msgstr "Rendert een menu tree voor administratieve doeleinden als een dropdown menu bovenaan de pagina." + +#: admin_menu.info:0 +msgid "Administration" +msgstr "Administration" + === added file 'sites/all/modules/admin_menu/translations/pt-br.po' --- sites/all/modules/admin_menu/translations/pt-br.po 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/translations/pt-br.po 2009-01-15 16:50:15 +0000 @@ -0,0 +1,112 @@ +msgid "" +msgstr "" +"Project-Id-Version: admin_menu v1.x\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2008-04-21 23:41+0200\n" +"Last-Translator: Jamil Daglees \n" +"Language-Team: unleashed mind \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" + +#: admin_menu.inc:126 +msgid "Administer" +msgstr "Administrar" + +#: admin_menu.inc:143 +msgid "Run cron" +msgstr "Rodar agendador de tarefas" + +#: admin_menu.inc:145 +msgid "Run updates" +msgstr "Rodar atualizações" + +#: admin_menu.inc:150 +msgid "Drupal.org" +msgstr "Drupal.org" + +#: admin_menu.inc:151 +msgid "Drupal issue queue" +msgstr "Drupal issue query" + +#: admin_menu.inc:162 +msgid "@title issue queue" +msgstr "@title issue query" + +#: admin_menu.inc:170 +msgid "Logout @name" +msgstr "Logout @name" + +#: admin_menu.inc:180 +msgid "Add !title" +msgstr "Adicionar !title" + +#: admin_menu.module:20 +msgid "Renders the administer menu tree as dropdown menu at the top of the window." +msgstr "Transforma o menu de administração em uma árvore em dropdown no topo da janela." + +#: admin_menu.module:299 +msgid "Current anonymous / authenticated users" +msgstr "Usuários anônimos / autenticados" + +#: admin_menu.module:322 +msgid "Administration Menu settings" +msgstr "Configurações do Menu de Administração" + +#: admin_menu.module:327 +msgid "None" +msgstr "Nenhum" + +#: admin_menu.module:327 +#, fuzzy +msgid "Id" +msgstr "ID" + +#: admin_menu.module:327 +msgid "Weight" +msgstr "Peso" + +#: admin_menu.module:327 +msgid "Parent id" +msgstr "ID Parente" + +#: admin_menu.module:330 +msgid "Display extra information for menu items in Drupal Administration Menu" +msgstr "Mostrar informações dos ítens de menu no Drupal Administration Menu" + +#: admin_menu.module:333 +msgid "If enabled, the chosen information will appear next to each menu item link." +msgstr "Se selecionado, a informação escolhida irá aparecer próximo a cada ítem do menu." + +#: admin_menu.module:337 +msgid "Display all menu items" +msgstr "Mostrar todos os ítens de menu" + +#: admin_menu.module:339 +msgid "Enable this option to disable user access checks for menu items, i.e. every menu item in the visible menu tree will be displayed to every user regardless of access permissions." +msgstr "Selecione essa opção para disabilitar verificações do acesso dos usuários, exemplo: cada ítem de menu visível aparecerá para todos os usuários indiferente das permissões." + +#: admin_menu.module:365 +msgid "Variable editor" +msgstr "Editor de variáveis" + +#: admin_menu.module:371 +msgid "Empty cache" +msgstr "Esvaziar o cache" + +#: admin_menu.info:0 +msgid "Administration Menu" +msgstr "Menu de Administração" + +#: admin_menu.info:0 +msgid "Renders a menu tree for administrative purposes as dropdown menu at the top of the window." +msgstr "Mostra uma árvore do menu para administração como um dropdown menu no topo da janela." + +#: admin_menu.info:0 +msgid "Administration" +msgstr "Administração" + === added file 'sites/all/modules/admin_menu/translations/ru.po' --- sites/all/modules/admin_menu/translations/ru.po 1970-01-01 00:00:00 +0000 +++ sites/all/modules/admin_menu/translations/ru.po 2009-01-15 16:50:15 +0000 @@ -0,0 +1,119 @@ +# translation of admin_menu-5.x-2.2.po to russian +# $Id: ru.po,v 1.1 2008/04/26 16:10:10 sun Exp $ +# LANGUAGE translation of Drupal (general) +# Copyright YEAR NAME +# Generated from files: +# admin_menu.inc,v 1.1.4.3 2007/12/02 06:25:51 sun +# admin_menu.module,v 1.26.2.3 2007/12/02 06:25:51 sun +# admin_menu.info,v 1.3.6.1 2007/12/02 06:25:51 sun +# +# Vlad Savitsky , 2008. +msgid "" +msgstr "" +"Project-Id-Version: admin_menu-5.x-2.2\n" +"POT-Creation-Date: 2007-12-02 07:40+0100\n" +"PO-Revision-Date: 2008-02-10 19:24+0200\n" +"Last-Translator: Vlad Savitsky \n" +"Language-Team: russian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: KBabel 1.11.4\n" + +#: admin_menu.inc:126 +msgid "Administer" +msgstr "Управление" + +#: admin_menu.inc:143 +msgid "Run cron" +msgstr "Запустить хронометр" + +#: admin_menu.inc:145 +msgid "Run updates" +msgstr "Запустить обновление" + +#: admin_menu.inc:150 +msgid "Drupal.org" +msgstr "Drupal.org" + +#: admin_menu.inc:151 +msgid "Drupal issue queue" +msgstr "Очередь ошибок Drupal" + +#: admin_menu.inc:162 +msgid "@title issue queue" +msgstr "@title очередь ошибок" + +#: admin_menu.inc:170 +msgid "Logout @name" +msgstr "@name уходит" + +#: admin_menu.inc:180 +msgid "Add !title" +msgstr "Создать !title" + +#: admin_menu.module:20 +msgid "Renders the administer menu tree as dropdown menu at the top of the window." +msgstr "Создаёт иерархическое меню в виде выпадающего меню на верху окна." + +#: admin_menu.module:299 +msgid "Current anonymous / authenticated users" +msgstr "Текущие анонимы / вошедшие" + +#: admin_menu.module:322 +msgid "Administration Menu settings" +msgstr "Параметры меню Администрирования" + +#: admin_menu.module:327 +msgid "None" +msgstr "Нет" + +#: admin_menu.module:327 +msgid "Id" +msgstr "Id" + +#: admin_menu.module:327 +msgid "Weight" +msgstr "Вес" + +#: admin_menu.module:327 +msgid "Parent id" +msgstr "ID родителя" + +#: admin_menu.module:330 +msgid "Display extra information for menu items in Drupal Administration Menu" +msgstr "Показывать дополнительную информацию для пунктов в Меню Администрирования Друпал" + +#: admin_menu.module:333 +msgid "If enabled, the chosen information will appear next to each menu item link." +msgstr "Если включено, выбранная информация будет появляться рядом с каждым пунктом меню." + +#: admin_menu.module:337 +msgid "Display all menu items" +msgstr "Показывать все пункты меню" + +#: admin_menu.module:339 +msgid "Enable this option to disable user access checks for menu items, i.e. every menu item in the visible menu tree will be displayed to every user regardless of access permissions." +msgstr "Включите этот параметр, чтобы отключить проверку прав доступа пользователей к пунктам меню. То есть каждый пункт в видимой иерархии меню будут показаны каждому пользователю не взирая на права доступа." + +#: admin_menu.module:365 +msgid "Variable editor" +msgstr "Редактор переменных" + +#: admin_menu.module:371 +msgid "Empty cache" +msgstr "Очистить кеш" + +#: admin_menu.info:0 +msgid "Administration Menu" +msgstr "Администрирование" + +#: admin_menu.info:0 +msgid "Renders a menu tree for administrative purposes as dropdown menu at the top of the window." +msgstr "Создаёт иерархическое меню для административных целей в виде выпадающего меню на верху окна." + +#: admin_menu.info:0 +msgid "Administration" +msgstr "Администрирование" + === added directory 'sites/all/modules/cck' === added file 'sites/all/modules/cck/cck.info' --- sites/all/modules/cck/cck.info 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/cck.info 2009-01-15 16:50:16 +0000 @@ -0,0 +1,14 @@ +; $Id$ +name = CCK Field UI +description = Add custom fields to users or content types using a User Interface. +package = CCK +core = 7.x +dependencies[] = field +files[] = cck.module +files[] = cck.install +files[] = includes/cck.admin.inc +files[] = includes/cck.text.inc +files[] = includes/cck.number.inc +files[] = includes/cck.list.inc +files[] = includes/cck.nodereference.inc +files[] = theme/theme.inc \ No newline at end of file === added file 'sites/all/modules/cck/cck.install' --- sites/all/modules/cck/cck.install 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/cck.install 2009-01-15 16:50:16 +0000 @@ -0,0 +1,98 @@ + array( + 'field_name' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => 'The name of the field.', + ), + 'bundle' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => FALSE, + 'description' => 'The name of the bundle, NULL for field settings.', + ), + 'setting_type' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => 'The type of setting that CCK is managing (field, instance, widget, display).', + ), + 'setting' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'description' => 'The name of the setting that CCK is managing.', + ), + 'setting_option' => array( + 'type' => 'text', + 'size' => 'medium', + 'not null' => TRUE, + 'description' => 'The custom value for this setting.', + ), + ), + ); + return $schema; +} + +/* +// TODO need to make the following changes to update existing data: +// Set up and move to content module to handle upgrade path: + +function content_update_7000() { + + - Add new columns for: + - deleted: defaults to 0 + - data: contains serialized array of complete $instance. + - Module-defined field settings are now at $field['settings'][...] + - Module-defined widget settings are now at $instance['widget']['settings'] + - Module-defined instance settings are at $instance['settings'] + - Field settings should be limited to settings that affect the schema, + all others should be instance settings. + - Display is now $instance['display'] instead of $instance['display_settings'] + - Display format now adds label to every context instead of one label + at the top level, for a more consistent structure. + - Rename display options, formatter names are now prefixed with field name. + - No more content type: $field['widget']['type_name'] + becomes $instance['bundle'] + - Required is now $instance['required'] + instead of $field['required'] + - Label is now $instance['label'] + instead of $field['widget']['label'] + - Description is now $instance['description'] + instead of $field['widget']['description'] + - Weight is now $instance['weight'] instead of $field['widget']['weight'] + - Text module textareas are now a separate field type so the + db schema won't change. + - Text module format columns are always set and don't change by field settings + so the db schema won't change. + - Text and Number with allowed values are now made into List field types. + +} +*/ \ No newline at end of file === added file 'sites/all/modules/cck/cck.js' --- sites/all/modules/cck/cck.js 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/cck.js 2009-01-15 16:50:15 +0000 @@ -0,0 +1,82 @@ +// $Id$ + +Drupal.behaviors.cckManageFields = { + attach: function(context) { + attachUpdateSelects(context); + } +}; + +function attachUpdateSelects(context) { + var widgetTypes = Drupal.settings.cckWidgetTypes; + var fields = Drupal.settings.cckFields; + + // Store the default text of widget selects. + $('#cck-field-overview .cck-widget-type-select', context).each(function() { + this.initialValue = this.options[0].text; + }); + + // 'Field type' select updates its 'Widget' select. + $('#cck-field-overview .cck-field-type-select', context).each(function() { + this.targetSelect = $('.cck-widget-type-select', $(this).parents('tr').eq(0)); + + $(this).change(function() { + var selectedFieldType = this.options[this.selectedIndex].value; + var options = (selectedFieldType in widgetTypes) ? widgetTypes[selectedFieldType] : [ ]; + this.targetSelect.cckPopulateOptions(options); + }); + + // Trigger change on initial pageload to get the right widget options + // when field type comes pre-selected (on failed validation). + $(this).trigger('change'); + }); + + // 'Existing field' select updates its 'Widget' select and 'Label' textfield. + $('#cck-field-overview .cck-field-select', context).each(function() { + this.targetSelect = $('.cck-widget-type-select', $(this).parents('tr').eq(0)); + this.targetTextfield = $('.cck-label-textfield', $(this).parents('tr').eq(0)); + + $(this).change(function(e, updateText) { + var updateText = (typeof(updateText) == 'undefined') ? true : updateText; + var selectedField = this.options[this.selectedIndex].value; + var selectedFieldType = (selectedField in fields) ? fields[selectedField].type : null; + var selectedFieldWidget = (selectedField in fields) ? fields[selectedField].widget : null + var options = (selectedFieldType && (selectedFieldType in widgetTypes)) ? widgetTypes[selectedFieldType] : [ ]; + this.targetSelect.cckPopulateOptions(options, selectedFieldWidget); + + if (updateText) { + $(this.targetTextfield).attr('value', (selectedField in fields) ? fields[selectedField].label : ''); + } + }); + + // Trigger change on initial pageload to get the right widget options + // and label when field type comes pre-selected (on failed validation). + $(this).trigger('change', false); + }); +} + +jQuery.fn.cckPopulateOptions = function(options, selected) { + return this.each(function() { + var disabled = false; + if (options.length == 0) { + options = [this.initialValue]; + disabled = true; + } + + // If possible, keep the same widget selected when changing field type. + // This is based on textual value, since the internal value might be + // different (optionwidgets_buttons vs. nodereference_buttons). + var previousSelectedText = this.options[this.selectedIndex].text; + + var html = ''; + jQuery.each(options, function(value, text) { + // Figure out which value should be selected. The 'selected' param + // takes precedence. + var is_selected = ((typeof(selected) !== 'undefined' && value == selected) || (typeof(selected) == 'undefined' && text == previousSelectedText)); + html += ''; + }); + + $(this) + .html(html) + .attr('disabled', disabled ? 'disabled' : ''); + }); +} \ No newline at end of file === added file 'sites/all/modules/cck/cck.module' --- sites/all/modules/cck/cck.module 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/cck.module 2009-01-15 16:50:16 +0000 @@ -0,0 +1,541 @@ + 'Fields', + 'description' => 'Manage custom fields that have been added to content types or users.', + 'page callback' => 'cck_fields_list', + 'access arguments' => array('administer content types'), + 'type' => MENU_NORMAL_ITEM, + ); + $items['admin/build/fields/list'] = array( + 'title' => 'Field list', + 'page callback' => 'cck_fields_list', + 'access arguments' => array('administer content types'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 1, + ); + // TODO D7 : remove, debug code + $items['admin/build/fields/field-info'] = array( + 'title' => 'Field Info (debug)', + 'page callback' => 'cck_debug_field_info', + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + ); + $items['admin/build/types/field-info'] = array( + 'title' => 'Field Info (debug)', + 'page callback' => 'cck_debug_field_info', + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + ); + // Create an 'Edit' tab for the user. + $items['admin/user/settings/edit'] = array( + 'title' => 'Edit', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('user_admin_settings'), + 'access arguments' => array('administer users'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + + + // Make sure this doesn't fire until field_bundles is working, + // and tables are updated, needed to avoid errors on initial installation. + if (!defined('MAINTENANCE_MODE')) { + // Create tabs for all possible bundles. + foreach (field_info_fieldable_types() as $obj_type => $info) { + foreach ($info['bundles'] as $bundle_name => $bundle_label) { + $admin_path = cck_bundle_admin_path($bundle_name); + $items[$admin_path .'/fields'] = array( + 'title' => 'Manage fields', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_field_overview_form', $bundle_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 1, + ); + // A dummy function to trigger a page refresh so that + // field menus get rebuilt correctly when new fields are added. + $items[$admin_path .'/fields/refresh'] = array( + 'title' => 'Refresh menu', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_field_menu_refresh', $bundle_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_CALLBACK, + 'weight' => 1, + ); + $items[$admin_path .'/display'] = array( + 'title' => 'Display fields', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_display_overview_form', $bundle_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + ); + + // 'Display fields' tab and context secondary tabs. + $tabs = cck_build_modes($obj_type); + foreach ($tabs as $key => $tab) { + $items[$admin_path .'/display/'. $key] = array( + 'title' => $tab['title'], + 'page arguments' => array('cck_display_overview_form', $bundle_name, $key), + 'access arguments' => array('administer content types'), + 'type' => $key == 'basic' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, + 'weight' => $key == 'basic' ? 0 : 1, + ); + } + + // Add tabs for any instances that are already created. + $instances = field_info_instances($bundle_name); + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $items[$admin_path .'/fields/'. $field_name] = array( + 'title' => $instance['label'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_field_edit_form', $bundle_name, $field_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + ); + $items[$admin_path .'/fields/'. $field_name .'/edit'] = array( + 'title' => 'Configure instance settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_field_edit_form', $bundle_name, $field_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + $items[$admin_path .'/fields/'. $field_name .'/field-settings'] = array( + 'title' => 'Configure field settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_field_settings_form', $bundle_name, $field_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + ); + $items[$admin_path .'/fields/'. $field_name .'/widget-type'] = array( + 'title' => 'Change widget type', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_widget_type_form', $bundle_name, $field_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + ); + $items[$admin_path .'/fields/'. $field_name .'/remove'] = array( + 'title' => 'Remove instance', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_field_remove_form', $bundle_name, $field_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + ); + } + } + } + } + return $items; +} + +function cck_debug_field_info() { + dsm(_field_info()); + return ''; +} + +/** + * Implementation of hook_theme(). + */ +function cck_theme() { + $path = drupal_get_path('module', 'cck') .'/theme'; + require_once "./$path/theme.inc"; + + return array( + 'cck_field_overview_form' => array( + 'template' => 'cck-admin-field-overview-form', + 'path' => $path, + 'arguments' => array('form' => NULL), + ), + 'cck_display_overview_form' => array( + 'template' => 'cck-admin-display-overview-form', + 'path' => $path, + 'arguments' => array('form' => NULL), + ), + ); +} + +/** + * Implementation of hook_field_attach_pre_view(). + */ +function cck_field_attach_view($output, $obj_type, &$object, $teaser, $page) { + // Add identifier to the object to be used to alter extra fields in the view. + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + $object->content['#fieldable'] = TRUE; + $object->content['#bundle'] = $bundle; + return $output; +} + +/** + * Implementation of hook_field_attach_form(). + */ +function cck_field_attach_form($obj_type, &$object, &$form, &$form_state) { + // Add identifier to the object to be used to alter extra fields in the form. + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + $form['#fieldable'] = TRUE; + $form['#bundle'] = $bundle; +} + +/** + * Implementation of hook_form_alter(). + */ +function cck_form_alter(&$form, $form_state, $form_id) { + if (!empty($form['#fieldable']) && isset($form['#bundle'])) { + $bundle_name = $form['#bundle']; + $extra = cck_extra_field_values($bundle_name); + foreach ($extra as $key => $value) { + if (isset($form[$key])) { + $form[$key]['#weight'] = $value['weight']; + } + } + } +} + +/** + * Implementation of profile_alter(). + * + * Change extra weights here after extra fields have been added. + * Hook field_attach_post_view() is too early, other fields aren't + * all added to the form at that point. + * + * TODO Can we make the view alter more generic? This requires that + * we specifically alter each individual 'view' for all fieldable entities. + */ +function cck_profile_alter($account) { + if (!empty($account->content['#fieldable']) && isset($account->content['#bundle'])) { + $bundle_name = $account->content['#bundle']; + $extra = cck_extra_field_values($bundle_name); + foreach ($extra as $key => $value) { + if (isset($account->content[$key])) { + $account->content[$key]['#weight'] = $value['weight']; + } + } + } +} + +/** + * Implementation of node_view_alter. + * + * Change extra weights here after extra fields have been added. + * Hook field_attach_post_view() is too early, other fields aren't + * all added to the form at that point. + * + * TODO Can we make the view alter more generic? This requires that + * we specifically alter each individual 'view' for all fieldable entities. + */ +function cck_node_view_alter(&$node, $teaser = FALSE, $page = FALSE) { + if (!empty($node->content['#fieldable']) && isset($node->content['#bundle'])) { + $bundle_name = $node->content['#bundle']; + $extra = cck_extra_field_values($bundle_name); + foreach ($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']; + } + } + } +} + +/** + * Implementation of hook_cck_extra_fields. + */ +function cck_cck_extra_fields($bundle_name) { + if ($bundle_name == 'user') { + return _cck_user_extra_fields(); + } + else { + return _cck_node_extra_fields($bundle_name); + } +} + +// TODO Add other user elements here. +// How to do this for users? The form elements are totally different +// than the view elements. +function _cck_user_extra_fields() { + $extra = array(); + $extra['account'] = array( + 'label' => 'User name and password', + 'description' => t('User module form element'), + 'weight' => -10 + ); + $extra['timezone'] = array( + 'label' => 'Timezone', + 'description' => t('User module form element.'), + 'weight' => 6 + ); + $extra['summary'] = array( + 'label' => 'History', + 'description' => t('User module view element.'), + 'weight' => 5 + ); + return $extra; +} + +function _cck_node_extra_fields($type_name) { + $type = node_get_types('type', $type_name); + $extra = array(); + + if ($type->has_title) { + $extra['title'] = array( + 'label' => $type->title_label, + 'description' => t('Node module element.'), + 'weight' => -5 + ); + } + if ($type->has_body) { + $extra['body_field'] = array( + 'label' => $type->body_label, + 'description' => t('Node module element.'), + 'weight' => 0, + 'view' => 'body' + ); + } + if (module_exists('locale') && variable_get("language_content_type_$type_name", 0)) { + $extra['language'] = array( + 'label' => t('Language'), + 'description' => t('Locale module element.'), + 'weight' => 0 + ); + } + if (module_exists('menu')) { + $extra['menu'] = array( + 'label' => t('Menu settings'), + 'description' => t('Menu module element.'), + 'weight' => -2 + ); + } + if (module_exists('taxonomy') && taxonomy_get_vocabularies($type_name)) { + $extra['taxonomy'] = array( + 'label' => t('Taxonomy'), + 'description' => t('Taxonomy module element.'), + 'weight' => -3 + ); + } + if (module_exists('upload') && variable_get("upload_$type_name", TRUE)) { + $extra['attachments'] = array( + 'label' => t('File attachments'), + 'description' => t('Upload module element.'), + '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_cck_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 field.module. + */ +function cck_extra_field_weight($bundle_name, $pseudo_field_name) { + $extra = cck_extra_field_values($bundle_name); + if (isset($extra[$pseudo_field_name])) { + return $extra[$pseudo_field_name]['weight']; + } +} + +function cck_extra_field_values($bundle_name) { + static $info = array(); + + if (empty($info)) { + $info = array(); + $bundles = field_info_bundles(); + foreach ($bundles as $bundle => $bundle_label) { + // Gather information about non-CCK 'fields'. + $extra = module_invoke_all('cck_extra_fields', $bundle); + drupal_alter('cck_extra_fields', $extra, $bundle); + + // Add saved weights. + foreach (variable_get('cck_extra_weights_'. $bundle, 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; + } + } + $info[$bundle] = $extra; + } + } + if (array_key_exists($bundle_name, $info)) { + return $info[$bundle_name]; + } + else { + return array(); + } +} + + +function cck_build_modes($obj_type, $tab_selector = NULL) { + static $info; + + if (!isset($info[$obj_type])) { + $info[$obj_type] = module_invoke_all('cck_build_modes'); + // Collect titles, and filter out non active modes. + $active_modes = field_build_modes($obj_type); + foreach ($info[$obj_type] as $tab => $values) { + $modes = array(); + foreach ($info[$obj_type][$tab]['build modes'] as $mode) { + if (isset($active_modes[$mode])) { + $modes[$mode] = $active_modes[$mode]; + } + } + if ($modes) { + $info[$obj_type][$tab]['build modes'] = $modes; + } + else { + unset($info[$obj_type][$tab]); + } + } + } + if ($tab_selector) { + return isset($info[$obj_type][$tab_selector]) ? $info[$obj_type][$tab_selector]['build modes'] : array(); + } + else { + return $info[$obj_type]; + } +} + +/** + * Implementation of hook_cck_build_modes(), on behalf of other core modules. + * + * @return + * An array describing the build modes defined by the module, grouped by tabs. + * + * Expected format: + * array( + * // A module can add its render modes to a tab defined by another module. + * 'tab1' => array( + * 'title' => t('The human-readable title of the tab'), + * 'build modes' => array('mymodule_mode1', 'mymodule_mode2'), + * ), + * 'tab2' => array( + * // ... + * ), + * ); + */ +function cck_cck_build_modes() { + $modes = array( + 'basic' => array( + 'title' => t('Basic'), + 'build modes' => array('teaser', 'full'), + ), + 'rss' => array( + 'title' => t('RSS'), + 'build modes' => array(NODE_BUILD_RSS), + ), + 'print' => array( + 'title' => t('Print'), + 'build modes' => array(NODE_BUILD_PRINT), + ), + 'search' => array( + 'title' => t('Search'), + 'build modes' => array(NODE_BUILD_SEARCH_INDEX, NODE_BUILD_SEARCH_RESULT), + ), + ); + return $modes; +} + +/** + * Implementation of hook_node_type(). + * + * TODO Right way to do this in D7? Not needed for users since the user type is never deleted. + * Content types could be deleted. Not sure about other entities. + * [yched] : fieldable will need to take care of calling field_rename_bundle(), field_delete_bundle() + * when appropriate anyway. Those functions - we don't have them yet :) - will need to call a hook + * so that CCK UI is notified of this. + */ +function cck_node_type($op, $info) { + switch ($op) { + case 'update': + if ($extra = variable_get('cck_extra_weights_'. $info->old_type, array())) { + variable_set('cck_extra_weights_'. $info->type, $extra); + variable_del('cck_extra_weights_'. $info->old_type); + } + break; + case 'delete': + variable_del('cck_extra_weights_'. $info->type); + break; + } +} + +/** + * Helper function to create the right administration path for a bundle. + */ +function cck_bundle_admin_path($bundle_name) { + if ($bundle_name == 'user') { + return 'admin/user/settings'; + } + else { + return 'admin/build/node-type/' . str_replace('_', '-', $bundle_name); + } +} \ No newline at end of file === added directory 'sites/all/modules/cck/includes' === added file 'sites/all/modules/cck/includes/cck.admin.inc' --- sites/all/modules/cck/includes/cck.admin.inc 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/includes/cck.admin.inc 2009-01-15 16:50:16 +0000 @@ -0,0 +1,1510 @@ + t('Operations'), 'colspan' => '4'),); + $rows = array(); + foreach ($names as $key => $name) { + $type = $types[$key]; + if (node_hook($type, 'form')) { + $bundle_url_str = str_replace('_', '-', $type->type); + $row = array( + check_plain($name), + check_plain($type->type), + ); + // Make the description smaller + $row[] = array('data' => filter_xss_admin($type->description), 'class' => 'description'); + // Set the edit column. + $row[] = array('data' => l(t('edit'), 'admin/build/node-type/'. $bundle_url_str)); + // Set links for managing fields. + // TODO: a hook to allow other cck modules to add more stuff? + $row[] = array('data' => l(t('manage fields'), 'admin/build/node-type/'. $bundle_url_str .'/fields')); + // Set the delete column. + if ($type->custom) { + $row[] = array('data' => l(t('delete'), 'admin/build/node-type/'. $bundle_url_str .'/delete')); + } + else { + $row[] = array('data' => ''); + } + + $rows[] = $row; + } + } + + if (empty($rows)) { + $rows[] = array(array('data' => t('No bundles available.'), 'colspan' => '7', 'class' => 'message')); + } + + return theme('table', $header, $rows); +} + +/** + * Menu callback; lists all defined fields for quick reference. + */ +function cck_fields_list() { + $instances = field_info_instances(); + $field_types = field_info_field_types(); + $bundles = field_info_bundles(); + $header = array(t('Field name'), t('Field type'), t('Used in')); + $rows = array(); + foreach ($instances as $bundle => $info) { + foreach ($info as $field_name => $instance) { + $field = field_info_field($field_name); + $rows[$field_name]['data'][0] = $field['locked'] ? t('@field_name (Locked)', array('@field_name' => $field_name)) : $field_name; + $rows[$field_name]['data'][1] = t($field_types[$field['type']]['label']); + $admin_path = cck_bundle_admin_path($bundle); + $rows[$field_name]['data'][2][] = l($bundles[$bundle], $admin_path .'/fields'); + $rows[$field_name]['class'] = $field['locked'] ? 'menu-disabled' : ''; + } + } + foreach ($rows as $field_name => $cell) { + $rows[$field_name]['data'][2] = implode(', ', $cell['data'][2]); + } + if (empty($rows)) { + $output = t('No fields have been defined for any content type yet.'); + } + else { + $output = theme('table', $header, $rows); + } + return $output; +} + +/** + * Helper function to display a message about inactive fields. + */ +function cck_inactive_message($bundle) { + // TODO : adapt to new D7 APIs + $inactive_fields = field_inactive_fields($bundle); + if (!empty($inactive_fields)) { + $field_types = _field_field_types(); + $widget_types = _field_widget_types($bundle); + drupal_set_message(t('This bundle has inactive fields. Inactive fields are not included in lists of available fields until their modules are enabled.'), 'error'); + foreach ($inactive_fields as $field_name => $field) { + drupal_set_message(t('!field (!field_name) is an inactive !field_type field that uses a !widget_type widget.', array( + '!field' => $field['label'], + '!field_name' => $field['field_name'], + '!field_type' => array_key_exists($field['type'], $field_types) ? $field_types[$field['type']]['label'] : $field['type'], + '!widget_type' => array_key_exists($field['widget']['type'], $widget_types) ? $widget_types[$field['widget']['type']]['label'] : $field['widget']['type'], + ))); + } + } +} + +/** + * Menu callback; listing of fields for a content type. + * + * Allows fields to be reordered and nested in fieldgroups using + * JS drag-n-drop. Non-field form elements can also be moved around. + */ +function cck_field_overview_form(&$form_state, $bundle) { + + cck_inactive_message($bundle); + $admin_path = cck_bundle_admin_path($bundle); + + // When displaying the form, make sure the list of fields + // is up-to-date. + if (empty($form_state['post'])) { + field_cache_clear(); + } + + // Gather bundle information. + $instances = field_info_instances($bundle); + $field_types = field_info_field_types(); + $widget_types = field_info_widget_types(); + + $extra = cck_extra_field_values($bundle); + + $groups = $group_options = array(); + if (module_exists('fieldgroup')) { + $groups = fieldgroup_groups($bundle); + $group_types = fieldgroup_types(); + $group_options = _fieldgroup_groups_label($bundle); + // Add the ability to group under the newly created row. + $group_options['_add_new_group'] = '_add_new_group'; + } + + // Store the default weights as we meet them, to be able to put the + //'add new' rows after them. + $weights = array(); + + $form = array( + '#tree' => TRUE, + '#bundle' => $bundle, + '#fields' => array_keys($instances), + '#groups' => array_keys($groups), + '#extra' => array_keys($extra), + '#field_rows' => array(), + '#group_rows' => array(), + ); + + // Fields. + foreach ($instances as $name => $instance) { + $field = field_info_field($instance['field_name']); + $admin_field_path = $admin_path .'/fields/'. $instance['field_name']; + $weight = $instance['weight']; + $form[$name] = array( + 'label' => array('#markup' => check_plain($instance['label'])), + 'field_name' => array('#markup' => $instance['field_name']), + 'type' => array( + '#markup' => l(t($field_types[$field['type']]['label']), $admin_field_path .'/field-settings', array( + 'attributes' => array('title' => t('Edit field settings.'))))), + 'widget_type' => array( + '#markup' => l(t($widget_types[$instance['widget']['type']]['label']), $admin_field_path .'/widget-type', array( + 'attributes' => array('title' => t('Change widget type.'))))), + 'configure' => array( + '#markup' => l(t('Configure'), $admin_field_path, array( + 'attributes' => array('title' => t('Edit instance settings.'))))), + 'remove' => array( + '#markup' => l(t('Remove'), $admin_field_path .'/remove', array( + 'attributes' => array('title' => t('Remove instance.'))))), + 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), + 'parent' => array('#type' => 'select', '#options' => $group_options, '#default_value' => ''), + 'prev_parent' => array('#type' => 'hidden', '#value' => ''), + 'hidden_name' => array('#type' => 'hidden', '#default_value' => $instance['field_name']), + '#leaf' => TRUE, + '#row_type' => 'field', + // TODO D7 : ?? + 'field' => array('#type' => 'value', '#value' => $field), + ); + + if (!empty($instance['locked'])) { + $form[$name]['configure'] = array('#value' => t('Locked')); + $form[$name]['remove'] = array(); + $form[$name]['#disabled_row'] = TRUE; + } + $form['#field_rows'][] = $name; + $weights[] = $weight; + } + + // Groups. + foreach ($groups as $name => $group) { + $weight = $group['weight']; + $form[$name] = array( + 'label' => array('#markup' => check_plain($group['label'])), + 'group_name' => array('#markup' => $group['group_name']), + 'group_type' => array('#markup' => t($group_types[$group['group_type']])), + 'configure' => array('#markup' => l(t('Configure'), $admin_path .'/groups/'. $group['group_name'])), + 'remove' => array('#markup' => l(t('Remove'), $admin_path .'/groups/'. $group['group_name'] .'/remove')), + 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), + 'parent' => array('#type' => 'hidden', '#default_value' => ''), + 'hidden_name' => array('#type' => 'hidden', '#default_value' => $group['group_name']), + '#root' => TRUE, + '#row_type' => 'group', + 'group' => array('#type' => 'value', '#value' => $group), + ); + // Adjust child fields rows. + foreach ($group['fields'] as $field_name => $field) { + $form[$field_name]['parent']['#default_value'] = $name; + $form[$field_name]['prev_parent']['#value'] = $name; + } + $form['#group_rows'][] = $name; + $weights[] = $weight; + } + + // Non-field elements. + foreach ($extra as $name => $label) { + $weight = $extra[$name]['weight']; + $form[$name] = array( + 'label' => array('#markup' => t($extra[$name]['label'])), + 'description' => array('#markup' => isset($extra[$name]['description']) ? $extra[$name]['description'] : ''), + 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), + 'parent' => array('#type' => 'hidden', '#default_value' => ''), + 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), + '#leaf' => TRUE, + '#root' => TRUE, + '#disabled_row' => TRUE, + '#row_type' => 'extra', + ); + $form['#field_rows'][] = $name; + $weights[] = $weight; + } + + // Additional row : add new field. + $weight = !empty($weights) ? max($weights) + 1 : 0; + $field_type_options = cck_field_type_options(); + $widget_type_options = cck_widget_type_options(NULL, TRUE); + if ($field_type_options && $widget_type_options) { + array_unshift($field_type_options, t('- Select a field type -')); + array_unshift($widget_type_options, t('- Select a widget -')); + $name = '_add_new_field'; + $form[$name] = array( + 'label' => array( + '#type' => 'textfield', + '#size' => 15, + '#description' => t('Label'), + ), + 'field_name' => array( + '#type' => 'textfield', + '#field_prefix' => 'field_', + '#size' => 15, + '#description' => t('Field name (a-z, 0-9, _)'), + ), + 'type' => array( + '#type' => 'select', + '#options' => $field_type_options, + '#description' => theme('advanced_help_topic', 'cck', 'fields') . t('Type of data to store.'), + ), + 'widget_type' => array( + '#type' => 'select', + '#options' => $widget_type_options, + '#description' => t('Form element to edit the data.'), + ), + 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), + 'parent' => array('#type' => 'select', '#options' => $group_options, '#default_value' => ''), + 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), + '#leaf' => TRUE, + '#add_new' => TRUE, + '#row_type' => 'add_new_field', + ); + $form['#field_rows'][] = $name; + } + + // Additional row : add existing field. + $existing_field_options = cck_existing_field_options($bundle); + if ($existing_field_options && $widget_type_options) { + $weight++; + array_unshift($existing_field_options, t('- Select an existing field -')); + $name = '_add_existing_field'; + $form[$name] = array( + 'label' => array( + '#type' => 'textfield', + '#size' => 15, + '#description' => t('Label'), + ), + 'field_name' => array( + '#type' => 'select', + '#options' => $existing_field_options, + '#description' => t('Field to share'), + ), + 'widget_type' => array( + '#type' => 'select', + '#options' => $widget_type_options, + '#description' => t('Form element to edit the data.'), + ), + 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), + 'parent' => array('#type' => 'select', '#options' => $group_options, '#default_value' => ''), + 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), + '#leaf' => TRUE, + '#add_new' => TRUE, + '#row_type' => 'add_existing_field', + ); + $form['#field_rows'][] = $name; + } + + // Additional row : add new group. + if (module_exists('fieldgroup')) { + $weight++; + $name = '_add_new_group'; + $form[$name] = array( + 'label' => array( + '#type' => 'textfield', + '#size' => 15, + '#description' => t('Label'), + ), + 'group_name' => array( + '#type' => 'textfield', + '#field_prefix' => 'group_', + '#size' => 15, + '#description' => t('Group name (a-z, 0-9, _)'), + ), + 'group_option' => array( + '#type' => 'hidden', + '#value' => '', + ), + 'group_type' => array( + '#type' => 'hidden', + '#value' => 'standard', + ), + 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), + 'parent' => array('#type' => 'hidden', '#default_value' => ''), + 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), + '#root' => TRUE, + '#add_new' => TRUE, + '#row_type' => 'add_new_group', + ); + $form['#group_rows'][] = $name; + } + + $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); + return $form; +} + +function cck_field_overview_form_validate($form, &$form_state) { + _cck_field_overview_form_validate_add_new($form, $form_state); + _cck_field_overview_form_validate_add_existing($form, $form_state); +} + +/** + * Helper function for cck_field_overview_form_validate. + * + * Validate the 'add new field' row. + */ +function _cck_field_overview_form_validate_add_new($form, &$form_state) { + + $field = $form_state['values']['_add_new_field']; + + // Validate if any information was provided in the 'add new field' row. + if (array_filter(array($field['label'], $field['field_name'], $field['type'], $field['widget_type']))) { + // No label. + if (!$field['label']) { + form_set_error('_add_new_field][label', t('Add new field: you need to provide a label.')); + } + + // No field name. + if (!$field['field_name']) { + form_set_error('_add_new_field][field_name', t('Add new field: you need to provide a field name.')); + } + // Field name validation. + else { + $field_name = $field['field_name']; + + // Add the 'field_' prefix. + if (substr($field_name, 0, 6) != 'field_') { + $field_name = 'field_'. $field_name; + form_set_value($form['_add_new_field']['field_name'], $field_name, $form_state); + } + + // Invalid field name. + if (!preg_match('!^field_[a-z0-9_]+$!', $field_name)) { + form_set_error('_add_new_field][field_name', t('Add new field: the field name %field_name is invalid. The name must include only lowercase unaccentuated letters, numbers, and underscores.', array('%field_name' => $field_name))); + } + if (strlen($field_name) > 32) { + form_set_error('_add_new_field][field_name', t('Add new field: the field name %field_name is too long. The name is limited to 32 characters, including the \'field_\' prefix.', array('%field_name' => $field_name))); + } + // A field named 'field_instance' would cause a tablename clash with {field_field_instance} + if ($field_name == 'field_instance') { + form_set_error('_add_new_field][field_name', t("Add new field: the name 'field_instance' is a reserved name.")); + } + + // Field name already exists. + // We need to check inactive fields as well, so we can't use field_fields(). + module_load_include('inc', 'content', 'includes/content.crud'); + $fields = field_read_fields(array(), TRUE); + $used = FALSE; + foreach ($fields as $existing_field) { + $used |= ($existing_field['field_name'] == $field_name); + } + if ($used) { + form_set_error('_add_new_field][field_name', t('Add new field: the field name %field_name already exists.', array('%field_name' => $field_name))); + } + } + + // No field type. + if (!$field['type']) { + form_set_error('_add_new_field][type', t('Add new field: you need to select a field type.')); + } + + // No widget type. + if (!$field['widget_type']) { + form_set_error('_add_new_field][widget_type', t('Add new field: you need to select a widget.')); + } + // Wrong widget type. + elseif ($field['type']) { + $widget_types = cck_widget_type_options($field['type']); + if (!isset($widget_types[$field['widget_type']])) { + form_set_error('_add_new_field][widget_type', t('Add new field: invalid widget.')); + } + } + } +} + +/** + * Helper function for cck_field_overview_form_validate. + * + * Validate the 'add existing field' row. + */ +function _cck_field_overview_form_validate_add_existing($form, &$form_state) { + + // The form element might be absent if no existing fields can be added to + // this content type + if (isset($form_state['values']['_add_existing_field'])) { + $field = $form_state['values']['_add_existing_field']; + + // Validate if any information was provided in the 'add existing field' row. + if (array_filter(array($field['label'], $field['field_name'], $field['widget_type']))) { + // No label. + if (!$field['label']) { + form_set_error('_add_existing_field][label', t('Add existing field: you need to provide a label.')); + } + + // No existing field. + if (!$field['field_name']) { + form_set_error('_add_existing_field][field_name', t('Add existing field: you need to select a field.')); + } + + // No widget type. + if (!$field['widget_type']) { + form_set_error('_add_existing_field][widget_type', t('Add existing field: you need to select a widget.')); + } + // Wrong widget type. + elseif ($field['field_name'] && ($existing_field = field_info_field($field['field_name']))) { + $widget_types = cck_widget_type_options($existing_field['type']); + if (!isset($widget_types[$field['widget_type']])) { + form_set_error('_add_existing_field][widget_type', t('Add existing field: invalid widget.')); + } + } + } + } +} + +function cck_field_overview_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $bundle = $form['#bundle']; + $admin_path = cck_bundle_admin_path($bundle); + + // Update field weights. + $extra = array(); + foreach ($form_values as $key => $values) { + // Groups are handled in fieldgroup_cck_overview_form_submit(). + if (in_array($key, $form['#fields'])) { + db_query("UPDATE {field_config_instance} SET weight = %d WHERE bundle = '%s' AND field_name = '%s'", + $values['weight'], $bundle, $key); + } + elseif (in_array($key, $form['#extra'])) { + $extra[$key] = $values['weight']; + } + } + + if ($extra) { + variable_set('cck_extra_weights_'. $bundle, $extra); + } + else { + variable_del('cck_extra_weights_'. $bundle); + } + + $destinations = array(); + + // Create new field. + $field = array(); + if (!empty($form_values['_add_new_field']['field_name'])) { + $values = $form_values['_add_new_field']; + $field = array( + 'field_name' => $values['field_name'], + 'type' => $values['type'], + ); + $instance = array( + 'field_name' => $field['field_name'], + 'bundle' => $bundle, + 'label' => $values['label'], + 'weight' => $values['weight'], + 'widget' => array( + 'type' => $values['widget_type'], + ), + ); + + // Create the field and instance. + try { + field_create_field($field); + field_create_instance($instance); + + // Rebuild the menu so we can navigate to the field settings screen. + //field_clear_type_cache(TRUE); + //menu_rebuild(); + $destinations[] = $admin_path .'/fields/refresh'; + $destinations[] = $admin_path .'/fields/'. $field['field_name'] .'/field-settings'; + $destinations[] = $admin_path .'/fields/'. $field['field_name'] .'/edit'; + + // Store new field information for fieldgroup submit handler. + $form_state['fields_added']['_add_new_field'] = $field['field_name']; + } + catch (Exception $e) { + drupal_set_message(t('There was a problem creating field %label: @message.', array( + '%label' => $values['label'], '@message' => $e->getMessage()))); + } + } + + // Add existing field. + if (!empty($form_values['_add_existing_field']['field_name'])) { + $values = $form_values['_add_existing_field']; + $field = field_info_field($values['field_name']); + if (!empty($field['locked'])) { + drupal_set_message(t('The field %label cannot be added because it is locked.', array('%label' => $field['field_name']))); + } + else { + $instance = array( + 'field_name' => $field['field_name'], + 'bundle' => $bundle, + 'label' => $values['label'], + 'weight' => $values['weight'], + 'widget' => array( + 'type' => $values['widget_type'], + ), + ); + + try { + field_create_instance($instance); + $destinations[] = $admin_path .'/fields/refresh'; + $destinations[] = $admin_path .'/fields/'. $instance['field_name'] .'/edit'; + // Store new field information for fieldgroup submit handler. + $form_state['fields_added']['_add_existing_field'] = $instance['field_name']; + } + catch (Exception $e) { + drupal_set_message(t('There was a problem creating field instance %label: @message.', array( + '%label' => $field['label'], '@message' => $e->getMessage()))); + } + } + } + + if ($destinations) { + $destinations[] = urldecode(substr(drupal_get_destination(), 12)); + unset($_REQUEST['destination']); + $form_state['redirect'] = cck_get_destinations($destinations); + } + + field_cache_clear(); +} + +/** + * Menu callback; presents a listing of fields display settings for a content type. + * + * Form includes form widgets to select which fields appear for teaser, full node + * and how the field labels should be rendered. + */ +function cck_display_overview_form(&$form_state, $bundle, $contexts_selector = 'basic') { + cck_inactive_message($bundle); + $admin_path = cck_bundle_admin_path($bundle); + + // Gather type information. + $entity = field_info_bundle_entity($bundle); + $instances = field_info_instances($bundle); + $field_types = field_info_field_types(); + + $groups = $group_options = array(); + if (module_exists('fieldgroup')) { + $groups = fieldgroup_groups($type['type']); + $group_options = _fieldgroup_groups_label($type['type']); + } + + $contexts = cck_build_modes($entity, $contexts_selector); + $form = array( + '#tree' => TRUE, + '#bundle' => $bundle, + '#fields' => array_keys($instances), + '#groups' => array_keys($groups), + '#contexts' => $contexts_selector, + ); + + if (empty($instances)) { + drupal_set_message(t('There are no fields configured yet. You can add new fields on the Manage fields page.', array('@link' => url($admin_path .'/fields'))), 'warning'); + return $form; + } + + // Fields. + $label_options = array( + 'above' => t('Above'), + 'inline' => t('Inline'), + 'hidden' => t(''), + ); + foreach ($instances as $name => $instance) { + $field = field_info_field($instance['field_name']); + $field_type = $field_types[$field['type']]; + $defaults = $instance['display']; + $weight = $instance['weight']; + + $form[$name] = array( + 'human_name' => array('#markup' => check_plain($instance['label'])), + 'weight' => array('#type' => 'value', '#value' => $weight), + 'parent' => array('#type' => 'value', '#value' => ''), + ); + + // Formatters. + $options = cck_formatter_options($field['type']); + $options['hidden'] = t(''); + + foreach ($contexts as $key => $value) { + $form[$name][$key]['label'] = array( + '#type' => 'select', + '#options' => $label_options, + '#default_value' => isset($defaults[$key]['label']) ? $defaults[$key]['label'] : 'hidden', + ); + $form[$name][$key]['type'] = array( + '#type' => 'select', + '#options' => $options, + '#default_value' => isset($defaults[$key]['type']) ? $defaults[$key]['type'] : 'default', + ); + // exclude from $content + $form[$name][$key]['exclude'] = array( + '#type' => 'checkbox', + '#options' => array(0 => t('Include'), 1 => t('Exclude')), + '#default_value' => isset($defaults[$key]['exclude']) ? $defaults[$key]['exclude'] : 0, + ); + } + } + + // Groups. + $label_options = array( + 'above' => t('Above'), + 'hidden' => t(''), + ); + $options = array( + 'no_style' => t('no styling'), + 'simple' => t('simple'), + 'fieldset' => t('fieldset'), + 'fieldset_collapsible' => t('fieldset - collapsible'), + 'fieldset_collapsed' => t('fieldset - collapsed'), + 'hidden' => t(''), + ); + foreach ($groups as $name => $group) { + $defaults = $group['settings']['display']; + $weight = $group['weight']; + + $form[$name] = array( + 'human_name' => array('#markup' => check_plain($group['label'])), + 'weight' => array('#type' => 'value', '#value' => $weight), + ); + foreach ($contexts as $key => $title) { + $form[$name][$key]['label'] = array( + '#type' => 'select', + '#options' => $label_options, + '#default_value' => isset($defaults[$key]['label']) ? $defaults[$key]['label'] : 'above', + ); + $form[$name][$key]['format'] = array( + '#type' => 'select', + '#options' => $options, + '#default_value' => isset($defaults[$key]['format']) ? $defaults[$key]['format'] : 'fieldset', + ); + // exclude in $content + $form[$name][$key]['exclude'] = array( + '#type' => 'checkbox', + '#options' => array(0 => t('Include'), 1 => t('Exclude')), + '#default_value' => isset($defaults[$key]['exclude']) ? $defaults[$key]['exclude'] : 0, + ); + } + foreach ($group['fields'] as $field_name => $field) { + $form[$field_name]['parent']['#value'] = $name; + } + } + + $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); + return $form; +} + +/** + * Submit handler for the display overview form. + */ +function cck_display_overview_form_submit($form, &$form_state) { + + module_load_include('inc', 'field', 'includes/field.crud'); + $form_values = $form_state['values']; + foreach ($form_values as $key => $values) { + // Groups are handled in fieldgroup_display_overview_form_submit(). + if (in_array($key, $form['#fields'])) { + $instance = field_info_instance($key, $form['#bundle']); + // We have some numeric keys here, so we can't use array_merge. + unset($values['weight'], $values['parent']); + $instance['display'] = $values + (array) $instance['display']; + field_update_instance($instance); + } + } + + drupal_set_message(t('Your settings have been saved.')); +} + +/** + * Return an array of field_type options. + */ +function cck_field_type_options() { + static $options; + + if (!isset($options)) { + $options = array(); + $field_types = field_info_field_types(); + $field_type_options = array(); + foreach ($field_types as $name => $field_type) { + // Skip field types which have no widget types. + if (cck_widget_type_options($name)) { + $options[$name] = t($field_type['label']); + } + } + asort($options); + } + return $options; +} + +/** + * Return an array of widget type options for a field type. + * + * If no field type is provided, returns a nested array of + * all widget types, keyed by field type human name. + */ +function cck_widget_type_options($field_type = NULL, $by_label = FALSE) { + static $options; + + if (!isset($options)) { + $options = array(); + foreach (field_info_widget_types() as $name => $widget_type) { + foreach ($widget_type['field types'] as $widget_field_type) { + $options[$widget_field_type][$name] = t($widget_type['label']); + } + } + } + + if ($field_type) { + return !empty($options[$field_type]) ? $options[$field_type] : array(); + } + elseif ($by_label) { + $field_types = field_info_field_types(); + $options_by_label = array(); + foreach ($options as $field_type => $widgets) { + $options_by_label[t($field_types[$field_type]['label'])] = $widgets; + } + return $options_by_label; + } + else { + return $options; + } +} + +/** + * Return an array of formatter options for a field type. + * + * If no field type is provided, returns a nested array of + * all formatters, keyed by field type. + */ +function cck_formatter_options($field_type = NULL) { + static $options; + + if (!isset($options)) { + $options = array(); + foreach (field_info_formatter_types() as $name => $formatter) { + foreach ($formatter['field types'] as $formatter_field_type) { + $options[$formatter_field_type][$name] = t($formatter['label']); + } + } + } + + if ($field_type) { + return !empty($options[$field_type]) ? $options[$field_type] : array(); + } + else { + return $options; + } +} + +/** + * Return an array of existing field to be added to a node type. + */ +function cck_existing_field_options($bundle) { + $bundles = field_info_instances(); + $options = array(); + $field_types = field_info_field_types(); + foreach ($bundles as $bundle_name => $instances) { + // No need to look in the currenht bundle. + if ($bundle_name != $bundle) { + foreach ($instances as $instance) { + $field = field_info_field($instance['field_name']); + // Don't show locked fields, or fields that are already in the current + // bundle. + if (empty($field['locked']) && !field_info_instance($field['field_name'], $bundle)) { + $text = t('@type: @field (@label)', array('@type' => t($field_types[$field['type']]['label']), '@label' => t($instance['label']), '@field' => $instance['field_name'])); + $options[$instance['field_name']] = (drupal_strlen($text) > 80) ? truncate_utf8($text, 77) . '...' : $text; + } + } + } + } + // Sort the list by type, then by field name, then by label. + asort($options); + return $options; +} + +/** + * Helper function to determine if a field has data in the database. + */ +function cck_field_has_data($field_name) { + $has_data = FALSE; + if (drupal_function_exists('field_db_tablename')) { + $table = field_db_tablename($field_name); + if (db_table_exists($table)) { + $query = db_select($table, 'f', array('fetch' => PDO::FETCH_ASSOC)); + $query->fields('f', array('entity_id')); + $query->condition('f.deleted', 0); + $results = $query->execute()->fetchAssoc(); + if (!empty($results)) { + $has_data = TRUE; + } + } + } + return $has_data; +} + +/** + * Menu callback; presents the field settings edit page. + */ +function cck_field_settings_form(&$form_state, $bundle, $field_name) { + // TODO D7 : why do we fetch from the db instead of fetching from field_info_field() / field_info_instance() ? + $instance = field_read_instance($field_name, $bundle); + $field = field_read_field($field_name); + $field_type = field_info_field_types($field['type']); + + $info_function = $field['module'] .'_field_info'; + $info = $info_function(); + $description = '

'. $info[$field['type']]['label'] .': '; + $description .= $info[$field['type']]['description'] .'

'; + $form['#prefix'] = '
'. $description .'
'; + + $description = '

'. t('These settings apply to the %field field everywhere it is used. These settings impact the way that data is stored in the database and cannot be changed once data has been created.', array( + '%field' => $instance['label'])) .'

'; + + // Create a form structure for the field values. + $form['field'] = array( + '#type' => 'fieldset', + '#title' => t('%field field settings', array('%field' => $instance['label'])), + '#description' => $description, + '#tree' => TRUE, + ); + + // See if data already exists for this field. + // If so, prevent changes to the field settings. + $has_data = cck_field_has_data($field_name); + if ($has_data) { + $form['field']['#description'] = '
'. t('There is data for this field in the database. The field settings can no longer be changed.' .'
') . $form['field']['#description']; + } + + // Build the non-configurable field values. + $form['field']['field_name'] = array('#type' => 'value', '#value' => $field_name); + $form['field']['type'] = array('#type' => 'value', '#value' => $field['type']); + $form['field']['module'] = array('#type' => 'value', '#value' => $field['module']); + $form['field']['active'] = array('#type' => 'value', '#value' => $field['active']); + + // Build the configurable field values. + $description = t('Maximum number of values users can enter for this field.'); + if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { + $description .= '
'. t("'Unlimited' will provide an 'Add more' button so the users can add as many values as they like."); + } + $form['field']['cardinality'] = array( + '#type' => 'select', + '#title' => t('Number of values'), + '#options' => array(FIELD_CARDINALITY_UNLIMITED => t('Unlimited')) + drupal_map_assoc(range(1, 10)), + '#default_value' => $field['cardinality'], + '#description' => $description, + '#disabled' => $has_data, + ); + + // Add settings provided by the field module. + $form['field']['settings'] = array(); + $additions = module_invoke($field_type['module'], 'field_settings_form', $field, $instance); + if (is_array($additions)) { + $form['field']['settings'] = $additions; + } + foreach ($form['field']['settings'] as $key => $setting) { + if (substr($key, 0, 1) != '#') { + $form['field']['settings'][$key]['#disabled'] = $has_data; + } + } + + $form['#bundle'] = $bundle; + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save field settings'), + ); + + return $form; +} + +/** + * Save a field's settings after editing. + */ +function cck_field_settings_form_submit($form, &$form_state) { + + $form_values = $form_state['values']; + $field = $form_values['field']; + + // Don't allow changes to fields with data. + if (cck_field_has_data($field['field_name'])) { + return; + } + + $bundle = $form['#bundle']; + $instance = field_info_instance($field['field_name'], $bundle); + + // Update the field. + cck_field_update_field($field); + + drupal_set_message(t('Updated field %label field settings.', array('%label' => $instance['label']))); + $form_state['redirect'] = cck_next_destination($form_state, $bundle); +} + +/** + * The Field API doesn't allow field updates, + * so we create a method here to update field if no data is created yet. + */ +function cck_field_update_field($field) { + + $field_types = field_info_field_types(); + $module = $field_types[$field['type']]['module']; + + // Make sure field settings at least have their default values. + if (!is_array($field['settings'])) { + $field['settings'] = array(); + } + $field['settings'] += (array) module_invoke($module, 'field_settings', $field['type']); + + drupal_write_record('field_config', $field, array('field_name')); + + // Clear caches + field_cache_clear(TRUE); +} + +/** + * Menu callback; select a widget for the field. + */ +function cck_widget_type_form(&$form_state, $bundle, $field_name) { + $instance = field_read_instance($field_name, $bundle); + $field = field_read_field($field_name); + + $field_type = field_info_field_types($field['type']); + $widget_type = field_info_widget_types($instance['widget']['type']); + $bundles = field_info_bundles(); + $bundle_label = $bundles[$bundle]; + + $form = array(); + $form['basic'] = array( + '#type' => 'fieldset', + '#title' => t('Change widget'), + ); + $form['basic']['widget_type'] = array( + '#type' => 'select', + '#title' => t('Widget type'), + '#required' => TRUE, + '#options' => cck_widget_type_options($field['type']), + '#default_value' => $instance['widget']['type'], + '#description' => t('The type of form element you would like to present to the user when creating this field in the %type type.', array('%type' => $bundle_label)), + ); + + $form['#instance'] = $instance; + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Continue'), + ); + + $form['#validate'] = array(); + $form['#submit'] = array('cck_widget_type_form_submit'); + + return $form; +} + +/** + * Submit the change in widget type. + */ +function cck_widget_type_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $instance = $form['#instance']; + $bundle = $instance['bundle']; + + // Set the right module information + $widget_type = field_info_widget_types($form_values['widget_type']); + $widget_module = $widget_type['module']; + + if (drupal_function_exists('field_update_instance')) { + $instance['widget']['type'] = $form_values['widget_type']; + $instance['widget']['module'] = $widget_module; + try { + field_update_instance($instance); + drupal_set_message(t('Changed the widget for field %label.', array( + '%label' => $instance['label']))); + } catch (FieldException $e) { + drupal_set_message(t('There was a problem changing the widget for field %label.', array( + '%label' => $instance['label']))); + } + } + else { + drupal_set_message(t('There was a problem changing the widget for field %label.', array( + '%label' => $instance['label']))); + } + + $form_state['redirect'] = cck_next_destination($form_state, $bundle); +} + +/** + * Menu callback; present a form for removing a field from a content type. + */ +function cck_field_remove_form(&$form_state, $bundle, $field_name) { + $field = field_info_field($field_name); + $instance = field_info_instance($field_name, $bundle); + $admin_path = cck_bundle_admin_path($bundle); + + $form = array(); + $form['bundle'] = array( + '#type' => 'value', + '#value' => $bundle, + ); + $form['field_name'] = array( + '#type' => 'value', + '#value' => $field_name, + ); + + $output = confirm_form($form, + t('Are you sure you want to remove the field %field?', array('%field' => $instance['label'])), + $admin_path .'/fields', + t('If you have any content left in this field, it will be lost. This action cannot be undone.'), + t('Remove'), t('Cancel'), + 'confirm' + ); + + if ($field['locked']) { + unset($output['actions']['submit']); + $output['description']['#markup'] = t('This field is locked and cannot be removed.'); + } + + return $output; +} + +/** + * Remove a field from a content type. + */ +function cck_field_remove_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $field = field_info_field($form_values['field_name']); + $instance = field_info_instance($form_values['field_name'], $form_values['bundle']); + $bundles = field_info_bundles(); + $bundle = $form_values['bundle']; + $bundle_label = $bundles[$bundle]; + + if ($field['locked']) { + return; + } + + $error = FALSE; + if (!empty($bundle) && $field && $form_values['confirm'] && drupal_function_exists('field_delete_instance')) { + try { + field_delete_instance($form_values['field_name'], $form_values['bundle']); + } catch (FieldException $e) { + $error = TRUE; + } + } + else { + $error = TRUE; + } + if ($error) { + drupal_set_message(t('There was a problem removing the %field from %type.', array( + '%field' => $instance['label'], + '%type' => $bundle_label))); + } + else { + drupal_set_message(t('The field %field has been removed from %type.', array( + '%field' => $instance['label'], + '%type' => $bundle_label))); + } + $form_state['redirect'] = cck_next_destination($form_state, $bundle); +} + +/** + * Menu callback; presents the field instance edit page. + */ +function cck_field_edit_form(&$form_state, $bundle, $field_name) { + $output = ''; + $instance = field_read_instance($field_name, $bundle); + $field = field_read_field($field_name); + + $form = array(); + + if (!empty($field['locked'])) { + $output = array(); + $output['locked'] = array( + '#markup' => t('The field %field is locked and cannot be edited.', array('%field' => $instance['label'])), + ); + return $output; + } + + $field_type = field_info_field_types($field['type']); + $widget_type = field_info_widget_types($instance['widget']['type']); + $bundles = field_info_bundles(); + + $title = isset($instance['label']) ? $instance['label'] : $instance['field_name']; + drupal_set_title(check_plain($title)); + + // Create a form structure for the instance values. + $form['instance'] = array( + '#tree' => TRUE, + '#type' => 'fieldset', + '#title' => t('%type settings', array('%type' => $bundles[$bundle])), + '#description' => t('These settings apply only to the %field field when used in the %type type.', array( + '%field' => $instance['label'], '%type' => $bundles[$bundle])), + ); + + // Build the non-configurable instance values. + $form['instance']['field_name'] = array('#type' => 'value', '#value' => $field_name); + $form['instance']['bundle'] = array('#type' => 'value', '#value' => $bundle); + $form['instance']['weight'] = array('#type' => 'value', '#value' => !empty($instance['weight']) ? $instance['weight'] : 0); + + // Build the configurable instance values. + $form['instance']['label'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#default_value' => !empty($instance['label']) ? $instance['label'] : $field_name, + '#required' => TRUE, + '#description' => t('The human-readable label for this field.'), + ); + $form['instance']['required'] = array( + '#type' => 'checkbox', + '#title' => t('Required'), + '#default_value' => !empty($instance['required']), + '#description' => t('Check if a value must be provided.'), + ); + + $form['instance']['description'] = array( + '#type' => 'textarea', + '#title' => t('Help text'), + '#default_value' => !empty($instance['description']) ? $instance['description'] : '', + '#rows' => 5, + '#description' => t('Instructions to present to the user below this field on the editing form.
Allowed HTML tags: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())), + '#required' => FALSE, + ); + + // Build the widget component of the instance. + $form['instance']['widget'] = array( + 'type' => array('#type' => 'value', '#value' => $instance['widget']['type']), + 'module' => array('#type' => 'value', '#value' => $widget_type['module']), + 'active' => array('#type' => 'value', '#value' => !empty($field['instance']['widget']['active']) ? 1 : 0), + ); + + // Add additional field instance settings from the widget module. + $additions = module_invoke($widget_type['module'], 'field_instance_settings_form', $instance); + if (is_array($additions)) { + $form['instance']['settings'] = $additions; + } + + // Add additional widget settings from the widget module. + $additions = module_invoke($widget_type['module'], 'widget_settings_form', $instance); + if (is_array($additions)) { + $form['instance']['widget']['settings'] = $additions; + $form['instance']['widget']['active']['#value'] = 1; + } + + // Add handling for default value if not provided by field. + if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT) { + // Store the original default value for use in programmed forms. + // Set '#default_value' instead of '#value' so programmed values + // can override whatever we set here. + $default_value = $instance['widget']['default_value']; + $default_value_php = $instance['widget']['default_value_php']; + $form['instance']['widget']['default_value'] = array( + '#type' => 'value', + '#default_value' => $instance['widget']['default_value'], + ); + $form['instance']['widget']['default_value_php'] = array( + '#type' => 'value', + '#default_value' => $instance['widget']['default_value_php'], + ); + + // We can't tell at the time we build the form if this is a programmed + // form or not, so we always end up adding the default value widget + // even if we won't use it. + cck_default_value_widget($field, $instance, $form, $form_state); + } + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save settings'), + ); + + return $form; +} + +/** + * Build default value fieldset. + */ +function cck_default_value_widget($field, $instance, &$form, &$form_state) { + $form['instance']['widget']['default_value_widget'] = array( + '#type' => 'fieldset', + '#title' => t('Default value'), + '#collapsible' => FALSE, + '#tree' => TRUE, + '#description' => t('The default value for this field, used when creating new content.') + ); + + // Make sure the default value is not a required field. + $instance['required'] = FALSE; + $instance['label'] = t('Default value'); + $instance['description'] = ''; + $items = $instance['widget']['default_value']; + // Set up form info that the default value widget will need to find in the form. + $form['#fields'] = array( + $field['field_name'] => array( + 'field' => $field, + 'instance' => $instance, + ), + ); + drupal_function_exists('field_field_form'); + // TODO allow multiple values (still requires some more work on 'add more' JS handler) + $form['instance']['widget']['default_value_widget'] += field_field_form(NULL, NULL, $field, $instance, $items, $form, $form_state, 0); + $form['#fields'][$field['field_name']]['form_path'] = array('instance', 'widget', 'default_value_widget', $field['field_name']); + + // Advanced: PHP code. + $form['instance']['widget']['default_value_widget']['advanced_options'] = array( + '#type' => 'fieldset', + '#title' => t('PHP code'), + '#collapsible' => TRUE, + '#collapsed' => empty($instance['widget']['default_value_php']), + ); + + if (user_access('Use PHP input for field settings (dangerous - grant with care)')) { + foreach (array_keys($field['columns']) as $column) { + $columns[] = t("'@column' => value for @column", array('@column' => $column)); + } + $sample = t("return array(\n 0 => array(@columns),\n // You'll usually want to stop here. Provide more values\n // if you want your 'default value' to be multi-valued:\n 1 => array(@columns),\n 2 => ...\n);", array('@columns' => implode(', ', $columns))); + + $form['instance']['widget']['default_value_widget']['advanced_options']['default_value_php'] = array( + '#type' => 'textarea', + '#title' => t('Code'), + '#default_value' => isset($instance['widget']['default_value_php']) ? $instance['widget']['default_value_php'] : '', + '#rows' => 6, + '#tree' => TRUE, + '#description' => t('Advanced usage only: PHP code that returns a default value. Should not include <?php ?> delimiters. If this field is filled out, the value returned by this code will override any value specified above. Expected format:
!sample
To figure out the expected format, you can use the devel load tab provided by devel module on a %type content page. The option to embed PHP code in the field definition is provided for backwards compatibility and could be deprecated in the future. It is strongly recommended that you move this code to a custom function in a custom module and simply identify the custom function in the box above!', array( + '!sample' => $sample, + '@link_devel' => 'http://www.drupal.org/project/devel', + '%type' => $instance['bundle'])), + ); + } + else { + $form['instance']['widget']['default_value_widget']['advanced_options']['markup_default_value_php'] = array( + '#type' => 'item', + '#title' => t('Code'), + '#value' => !empty($instance['widget']['default_value_php']) ? ''. check_plain($instance['widget']['default_value_php']) .'' : t('<none>'), + '#description' => empty($instance['widget']['default_value_php']) ? t("You're not allowed to input PHP code.") : t('This PHP code was set by an administrator and will override any value specified above.'), + ); + } +} + +/** + * Validate a field's settings. + */ +function cck_field_edit_form_validate($form, &$form_state) { + + $form_values = $form_state['values']; + $instance = $form_values['instance']; + $field = field_info_field($instance['field_name']); + + $field_type = field_info_field_types($field['type']); + $widget_type = field_info_widget_types($instance['widget']['type']); + + // TODO D7 : deprecate hook_*_settings_form_validate in favor of regular FAPI validation ? + module_invoke($widget_type['module'], 'widget_settings_form_validate', array_merge($instance, $form_values)); + module_invoke($field_type['module'], 'field_settings_form_validate', array_merge($instance, $form_values)); + + // If field.module is handling the default value, + // validate the result using the field validation. + if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT) { + + // If this is a programmed form, get rid of the default value widget, + // we have the default values already. + if ($form['#programmed']) { + form_set_value(array('#parents' => array('instance', 'widget', 'default_value_widget')), NULL, $form_state); + return; + } + + if (isset($form_values['instance']['widget']['default_value_php']) && + ($php = trim($form_values['instance']['widget']['default_value_php']))) { + ob_start(); + $return = eval($php); + ob_end_clean(); + if (!is_array($return)) { + $error = TRUE; + } + else { + foreach ($return as $item) { + if (!is_array($item)) { + $error = TRUE; + break; + } + } + } + if ($error) { + foreach (array_keys($field['columns']) as $column) { + $columns[] = t("'@column' => value for @column", array('@column' => $column)); + } + $sample = t("return array(\n 0 => array(@columns),\n You'll usually want to stop here. Provide more values\n if you want your 'default value' to be multi-valued:\n 1 => array(@columns),\n 2 => ...\n);", array('@columns' => implode(', ', $columns))); + + form_set_error('default_value_php', t('The default value PHP code returned an incorrect value.
Expected format:
!sample
Returned value: @value', array( + '!sample' => $sample, + '@value' => print_r($return, TRUE)))); + return; + } + else { + $default_value = $return; + $is_code = TRUE; + form_set_value(array('#parents' => array('instance', 'widget', 'default_value_php')), $php, $form_state); + form_set_value(array('#parents' => array('instance', 'widget', 'default_value')), array(), $form_state); + } + } + elseif (!empty($form_values['instance']['widget']['default_value_widget'])) { + // Fields that handle their own multiple values may use an expected + // value as the top-level key, so just pop off the top element. + $key = array_shift(array_keys($form_values['instance']['widget']['default_value_widget'])); + $default_value = $form_values['instance']['widget']['default_value_widget'][$key]; + $is_code = FALSE; + form_set_value(array('#parents' => array('instance', 'widget', 'default_value_php')), '', $form_state); + form_set_value(array('#parents' => array('instance', 'widget', 'default_value')), $default_value, $form_state); + } + if (isset($default_value)) { + $node = array(); + $node[$field['field_name']] = $default_value; + $field['required'] = FALSE; + $field_function = $field_type['module'] .'_field'; + + $errors_before = form_get_errors(); + + // Widget now does its own validation, should be no need + // to add anything for widget validation here. + if (drupal_function_exists($field_function)) { + $field_function('validate', $node, $field, $default_value, $form, NULL); + } + // The field validation routine won't set an error on the right field, + // so set it here. + $errors_after = form_get_errors(); + if (count($errors_after) > count($errors_before)) { + if (trim($form_values['default_value_php'])) { + form_set_error('default_value_php', t("The PHP code for 'default value' returned @value, which is invalid.", array( + '@value' => print_r($default_value, TRUE)))); + } + else { + form_set_error('default_value', t('The default value is invalid.')); + } + } + } + } +} + +/** + * Save instance settings after editing. + */ +function cck_field_edit_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $instance = $form_values['instance']; + + // Make sure the default value widget does not get stored. + if (isset($instance['widget']['default_value_widget'])) { + unset($instance['widget']['default_value_widget']); + } + + // TODO, move this to cck.text.inc and cck.number.inc?? + // Make sure the allowed values fieldset does not get stored. + if (isset($instance['settings']['allowed_values_fieldset'])) { + $instance['settings'] += $instance['settings']['allowed_values_fieldset']; + unset($instance['settings']['allowed_values_fieldset']); + } + + // Update the instance. + module_load_include('inc', 'field', 'includes/field.crud'); + field_update_instance($instance); + + drupal_set_message(t('Saved %label configuration.', array('%label' => $instance['label']))); + + $form_state['redirect'] = cck_next_destination($form_state, $instance['bundle']); +} + +/** + * Helper functions to handle multipage redirects. + */ +function cck_get_destinations($destinations) { + $query = array(); + $path = array_shift($destinations); + if ($destinations) { + $query['destinations'] = $destinations; + } + return array($path, $query); +} + +function cck_next_destination(&$form_state, $bundle) { + $destinations = !empty($_REQUEST['destinations']) ? $_REQUEST['destinations'] : array(); + if (!empty($destinations)) { + unset($_REQUEST['destinations']); + return cck_get_destinations($destinations); + } + else { + $admin_path = cck_bundle_admin_path($bundle); + return $admin_path .'/fields'; + } +} + +/** + * Dummy function to force a page refresh so + * menu_rebuild() will work right when creating a new field + * that creates a new menu item. + */ +function cck_field_menu_refresh(&$form_state, $bundle) { + menu_rebuild(); + $destinations = cck_next_destination($form_state, $bundle); + if (is_array($destinations)) { + $path = array_shift($destinations); + drupal_goto($path, $destinations); + } + else { + drupal_goto($destinations); + } +} + +/** + * Helper function to order fields and groups when theming (preprocessing) + * overview forms. + * + * The $form is passed by reference because we assign depths as parenting + * relationships are sorted out. + */ +function _cck_overview_order(&$form, $field_rows, $group_rows) { + // Put weight and parenting values into a $dummy render structure + // and let drupal_render figure out the corresponding row order. + $dummy = array(); + // Group rows: account for weight. + if (module_exists('fieldgroup')) { + foreach ($group_rows as $name) { + $dummy[$name] = array('#markup' => $name .' ', '#weight' => $form[$name]['weight']['#value']); + } + } + // Field rows : account for weight and parenting. + foreach ($field_rows as $name) { + $dummy[$name] = array('#markup' => $name .' ', '#type' => 'markup', '#weight' => $form[$name]['weight']['#value']); + if (module_exists('fieldgroup')) { + if ($parent = $form[$name]['parent']['#value']) { + $form[$name]['#depth'] = 1; + $dummy[$parent][$name] = $dummy[$name]; + unset($dummy[$name]); + } + } + } + return $dummy ? explode(' ', trim(drupal_render($dummy))) : array(); +} + +/** + * Helper form element validator : integer. + */ +function _element_validate_integer($element, &$form_state) { + $value = $element['#value']; + if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) { + form_error($element, t('%name must be an integer.', array('%name' => $element['#title']))); + } +} + +/** + * Helper form element validator : integer > 0. + */ +function _element_validate_integer_positive($element, &$form_state) { + $value = $element['#value']; + if ($value !== '' && (!is_numeric($value) || intval($value) != $value || $value <= 0)) { + form_error($element, t('%name must be a positive integer.', array('%name' => $element['#title']))); + } +} + +/** + * Helper form element validator : number. + */ +function _element_validate_number($element, &$form_state) { + $value = $element['#value']; + if ($value != '' && !is_numeric($value)) { + form_error($element, t('%name must be a number.', array('%name' => $element['#title']))); + } +} \ No newline at end of file === added file 'sites/all/modules/cck/includes/cck.list.inc' --- sites/all/modules/cck/includes/cck.list.inc 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/includes/cck.list.inc 2009-01-15 16:50:16 +0000 @@ -0,0 +1,103 @@ + $field['field_name']))->fetchField(); +} + +/** + * A custom function to return allowed values from PHP code. + */ +function cck_list_allowed_values_php($field) { + $allowed_values = array(); + $php = cck_list_get_allowed_values_php($field); + if (!empty($php)) { + ob_start(); + $result = eval($php); + if (is_array($result)) { + $allowed_values = $result; + } + ob_end_clean(); + } + return $allowed_values; +} + +/** + * Implementation of hook_field_settings_form() + * on behalf of core List module. + */ +function list_field_settings_form($field, $instance) { + + $form = array( + '#element_validate' => array('list_field_settings_form_validate'), + ); + + $defaults = list_field_settings($field['type']); + $settings = array_merge($defaults, $field['settings']); + $settings['allowed_values_php'] = cck_list_get_allowed_values_php($field); + + $form['allowed_values'] = array( + '#type' => 'textarea', + '#title' => t('Allowed values list'), + '#default_value' => $settings['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 must be a %type value. The label is optional, and the key will be used as the label if no label is specified.
Allowed HTML tags in labels: @tags', array('%type' => $field['type'] == 'list_text' ? 'text' : 'numeric', '@tags' => _field_filter_xss_display_allowed_tags())), + ); + $form['allowed_values_function'] = array( + '#type' => 'textfield', + '#title' => t('Allowed values function'), + '#default_value' => $settings['allowed_values_function'], + '#description' => t('The name of a function that will return the allowed values list. If this field is filled out, the array returned by this code will override the allowed values list above.'), + ); + $form['advanced_options'] = array( + '#type' => 'fieldset', + '#title' => t('PHP code for allowed values'), + '#collapsible' => TRUE, + '#collapsed' => empty($settings['allowed_values_php']), + ); + if (user_access('Use PHP input for field settings (dangerous - grant with care)')) { + $form['advanced_options']['allowed_values_php'] = array( + '#type' => 'textarea', + '#title' => t('Code'), + '#default_value' => $settings['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, and the allowed values function will be set to 'cck_list_allowed_values_php' to execute this custom code. The option to embed PHP code in the field definition is provided for backwards compatibility and could be deprecated in the future. It is strongly recommended that you move this code to a custom function in a custom module and simply identify the custom function in the box above!"), + ); + } + else { + $form['advanced_options']['markup_allowed_values_php'] = array( + '#type' => 'item', + '#title' => t('Code'), + '#value' => !empty($settings['allowed_values_php']) ? ''. check_plain($settings['allowed_values_php']) .'' : t('<none>'), + '#description' => empty($settings['allowed_values_php']) ? t("You're not allowed to input PHP code.") : t('This PHP code was set by an administrator and will override the allowed values list and allowed values functions shown above.'), + ); + } + return $form; +} + +/** + * Handle Allowed values PHP code. + */ +function list_field_settings_form_validate($form, &$form_state) { + $form_values = $form_state['values']; + $field = $form_values['field']; + $field_name = $field['field_name']; + $option = $form_values['field']['settings']['advanced_options']['allowed_values_php']; + db_query("DELETE FROM {cck_field_settings} WHERE setting='field' AND setting_type='allowed_values_php' AND field_name = ':field_name'", array(':field_name' => $field['field_name'])); + if (!empty($option)) { + $record = array( + 'field_name' => $field_name, + 'bundle' => NULL, + 'setting_type' => 'field', + 'setting' => 'allowed_values_php', + 'setting_option' => $option, + ); + drupal_write_record('cck_field_settings', $record, array()); + form_set_value($form['allowed_values_function'], 'cck_list_allowed_values_php', $form_state); + form_set_value($form['advanced_options'], NULL, $form_state); + } +} === added file 'sites/all/modules/cck/includes/cck.nodereference.inc' --- sites/all/modules/cck/includes/cck.nodereference.inc 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/includes/cck.nodereference.inc 2009-01-15 16:50:16 +0000 @@ -0,0 +1,17 @@ + 'checkboxes', + '#title' => t('Content types that can be referenced'), + '#multiple' => TRUE, + '#default_value' => is_array($field['referenceable_types']) ? $field['referenceable_types'] : array(), + '#options' => array_map('check_plain', node_get_types('names')), + ); + return $form; +} \ No newline at end of file === added file 'sites/all/modules/cck/includes/cck.number.inc' --- sites/all/modules/cck/includes/cck.number.inc 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/includes/cck.number.inc 2009-01-15 16:50:15 +0000 @@ -0,0 +1,71 @@ + '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' => $field['settings']['precision'], + ); + $form['scale'] = array( + '#type' => 'select', + '#options' => drupal_map_assoc(range(0, 6)), + '#title' => t('Scale'), + '#description' => t('The number of digits to the right of the decimal.'), + '#default_value' => $field['settings']['scale'], + ); + $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' => $field['settings']['decimal'], + ); + } + return $form; +} + +function number_field_instance_settings_form($instance) { + $widget = $instance['widget']; + $field = field_info_field($instance['field_name']); + $field_type = field_info_field_types($field['type']); + $defaults = field_info_instance_settings($field['type']); + $settings = array_merge($defaults, $instance['settings']); + + $form['min'] = array( + '#type' => 'textfield', + '#title' => t('Minimum'), + '#element_validate' => array('_element_validate_number'), + '#default_value' => $instance['settings']['min'], + '#description' => t('The minimum value that should be allowed in this field. Leave blank for no minimum.') + ); + $form['max'] = array( + '#type' => 'textfield', + '#title' => t('Maximum'), + '#element_validate' => array('_element_validate_number'), + '#default_value' => $instance['settings']['max'], + '#description' => t('The maximum value that should be allowed in this field. Leave blank for no maximum.') + ); + $form['prefix'] = array( + '#type' => 'textfield', + '#title' => t('Prefix'), + '#size' => 60, + '#default_value' => $instance['settings']['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['suffix'] = array( + '#type' => 'textfield', + '#title' => t('Suffix'), + '#size' => 60, + '#default_value' => $instance['settings']['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).'), + ); + return $form; +} \ No newline at end of file === added file 'sites/all/modules/cck/includes/cck.text.inc' --- sites/all/modules/cck/includes/cck.text.inc 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/includes/cck.text.inc 2009-01-15 16:50:16 +0000 @@ -0,0 +1,72 @@ + 'textfield', + '#title' => t('Maximum length'), + '#default_value' => !empty($settings['max_length']) && is_numeric($settings['max_length']) ? $settings['max_length'] : $defaults['max_length'], + '#required' => FALSE, + '#element_validate' => array('_element_validate_integer_positive'), + '#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'), + ); + + return $form; +} + +/** + * Implementation of hook_widget_settings_form() + * on behalf of core Text module. + */ +function text_widget_settings_form($instance) { + $widget = $instance['widget']; + $defaults = field_info_widget_settings($widget['type']); + $settings = array_merge($defaults, $widget['settings']); + $field = field_info_field($instance['field_name']); + if ($widget['type'] == 'text_textfield') { + $form['size'] = array( + '#type' => 'textfield', + '#title' => t('Size of textfield'), + '#default_value' => $settings['size'], + '#element_validate' => array('_element_validate_integer_positive'), + '#required' => TRUE, + ); + } + else { + $form['rows'] = array( + '#type' => 'textfield', + '#title' => t('Rows'), + '#default_value' => $settings['rows'], + '#element_validate' => array('_element_validate_integer_positive'), + '#required' => TRUE, + ); + } + return $form; +} + +/** + * Implementation of hook_field_instance_settings_form() + * on behalf of core Text module. + */ +function text_field_instance_settings_form($instance) { + $widget = $instance['widget']; + $field = field_info_field($instance['field_name']); + $field_type = field_info_field_types($field['type']); + $defaults = field_info_instance_settings($field['type']); + $settings = array_merge($defaults, $instance['settings']); + $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' => !empty($settings['text_processing']) && is_numeric($settings['text_processing']) ? $settings['text_processing'] : $defaults['text_processing'], + '#options' => $options, + ); + return $form; +} \ No newline at end of file === added directory 'sites/all/modules/cck/modules' === added directory 'sites/all/modules/cck/modules/fieldgroup' === added file 'sites/all/modules/cck/modules/fieldgroup/fieldgroup.css' --- sites/all/modules/cck/modules/fieldgroup/fieldgroup.css 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/modules/fieldgroup/fieldgroup.css 2009-01-15 16:50:15 +0000 @@ -0,0 +1,6 @@ +div.fieldgroup { + margin:.5em 0 1em 0; +} +div.fieldgroup .content { + padding-left:1em; +} === added file 'sites/all/modules/cck/modules/fieldgroup/fieldgroup.info' --- sites/all/modules/cck/modules/fieldgroup/fieldgroup.info 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/modules/fieldgroup/fieldgroup.info 2009-01-15 16:50:16 +0000 @@ -0,0 +1,8 @@ +; $Id$ +name = Fieldgroup +description = Create display groups for CCK fields. +dependencies[] = field +dependencies[] = cck +package = CCK +core = 7.x +files[]=fieldgroup.module \ No newline at end of file === added file 'sites/all/modules/cck/modules/fieldgroup/fieldgroup.install' --- sites/all/modules/cck/modules/fieldgroup/fieldgroup.install 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/modules/fieldgroup/fieldgroup.install 2009-01-15 16:50:16 +0000 @@ -0,0 +1,46 @@ + array( + 'group_type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => 'standard'), + 'bundle' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'group_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'label' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), + 'settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE), + 'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), + ), + 'primary key' => array('bundle', 'group_name'), + ); + + $schema['field_group_fields'] = array( + 'fields' => array( + 'bundle' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'group_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'field_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + ), + 'primary key' => array('bundle', 'group_name', 'field_name'), + ); + + return $schema; +} \ No newline at end of file === added file 'sites/all/modules/cck/modules/fieldgroup/fieldgroup.module' --- sites/all/modules/cck/modules/fieldgroup/fieldgroup.module 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/modules/fieldgroup/fieldgroup.module 2009-01-15 16:50:15 +0000 @@ -0,0 +1,768 @@ +content. + * - hook_fieldgroup_form: Alter the group portion of the node form. + * - hook_fieldgroup_types: Add additional fieldgroup group_types. + * - hook_fieldgroup_default_settings: Add additional fieldgroup default settings. + * - hook_fieldgroup_save: Do additional processing when a fieldgroup is saved. + */ +/** + * Implementation of hook_init(). + */ +function fieldgroup_init() { + drupal_add_css(drupal_get_path('module', 'fieldgroup') .'/fieldgroup.css'); +} + +/** + * Implementation of hook_menu(). + */ +function fieldgroup_menu() { + $items = array(); + + // Make sure this doesn't fire until field_info_fieldable_types() is working, + // needed to avoid errors on initial installation. + if (!defined('MAINTENANCE_MODE')) { + // Create tabs for all possible bundles. + $bundles = field_info_bundles(); + foreach ($bundles as $bundle_name => $bundle_label) { + $admin_path = cck_bundle_admin_path($bundle_name); + $items[$admin_path .'/groups/%'] = array( + 'title' => 'Edit group', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('fieldgroup_group_edit_form', $bundle_name, 5), + 'access arguments' => array('administer content types'), + 'type' => MENU_CALLBACK, + ); + $items[$admin_path .'/groups/%/remove'] = array( + 'title' => 'Edit group', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('fieldgroup_remove_group', $bundle_name, 5), + 'access arguments' => array('administer content types'), + 'type' => MENU_CALLBACK, + ); + } + } + return $items; +} + +/** + * Implementation of hook_theme(). + */ +function fieldgroup_theme() { + return array( + 'fieldgroup_simple' => array( + 'template' => 'fieldgroup', + 'arguments' => array('element' => NULL), + ), + 'fieldgroup_fieldset' => array( + 'arguments' => array('element' => NULL), + ), + 'fieldgroup_display_overview_form' => array( + 'arguments' => array('form' => NULL), + ), + ); +} + +/** + * Implementation of hook_elements(). + */ +function fieldgroup_elements() { + return array( + 'fieldgroup_simple' => array(), + 'fieldgroup_fieldset' => array('#collapsible' => FALSE, '#collapsed' => FALSE, '#value' => NULL,), + ); +} + +/** + * Implementation of hook_fieldapi(). + */ +function fieldgroup_field_fieldapi($op, $field) { + switch ($op) { + case 'delete instance': + db_query("DELETE FROM {field_group_fields} WHERE field_name = '%s'", $field['field_name']); + break; + } + cache_clear_all('fieldgroup_data', 'cache_field'); +} + +function fieldgroup_group_edit_form(&$form_state, $bundle, $group_name) { + $groups = fieldgroup_groups($bundle); + + if (!$group = $groups[$group_name]) { + drupal_not_found(); + exit; + } + + $form['label'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#default_value' => $group['label'], + '#required' => TRUE, + ); + + // Set a default value for group type early in the form so it + // can be overridden by subsequent form elements added by other modules. + $group_type = !empty($group['group_type']) ? $group['group_type'] : 'standard'; + $form['group_type'] = array('#type' => 'hidden', '#default_value' => $group_type); + + $form['settings']['#tree'] = TRUE; + $form['settings']['form'] = array( + '#type' => 'fieldset', + '#title' => t('Form settings'), + '#description' => t('These settings apply to the group in the node editing form.'), + ); + $form['settings']['form']['style'] = array( + '#type' => 'radios', + '#title' => t('Style'), + '#default_value' => $group['settings']['form']['style'], + '#options' => array( + 'fieldset' => t('always open'), + 'fieldset_collapsible' => t('collapsible'), + 'fieldset_collapsed' => t('collapsed'), + ) + ); + $form['settings']['form']['description'] = array( + '#type' => 'textarea', + '#title' => t('Help text'), + '#default_value' => $group['settings']['form']['description'], + '#rows' => 5, + '#description' => t('Instructions to present to the user on the editing form.'), + '#required' => FALSE, + ); + $form['settings']['display'] = array( + '#type' => 'fieldset', + '#title' => t('Display settings'), + '#description' => t('These settings apply to the group on the display.'), + ); + $form['settings']['display']['description'] = array( + '#type' => 'textarea', + '#title' => t('Description'), + '#default_value' => $group['settings']['display']['description'], + '#rows' => 5, + '#description' => t('A description of the group.'), + '#required' => FALSE, + ); + + foreach (array_keys(field_build_modes(field_info_bundle_entity($bundle))) as $key) { + $form['settings']['display'][$key]['format'] = array('#type' => 'value', '#value' => isset($group['settings']['display'][$key]['format']) ? $group['settings']['display'][$key]['format'] : 'fieldset'); + $form['settings']['display'][$key]['exclude'] = array('#type' => 'value', '#value' => isset($group['settings']['display'][$key]['exclude']) ? $group['settings']['display'][$key]['exclude'] : 0); + } + + $form['settings']['display']['label'] = array('#type' => 'value', '#value' => $group['settings']['display']['label']); + $form['weight'] = array('#type' => 'hidden', '#default_value' => $group['weight']); + $form['group_name'] = array('#type' => 'hidden', '#default_value' => $group_name); + + $form['#bundle'] = $bundle; + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#weight' => 10, + ); + + return $form; +} + +function fieldgroup_group_edit_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $bundle = $form['#bundle']; + fieldgroup_save_group($bundle, $form_values); + $form_state['redirect'] = cck_bundle_admin_path($bundle) .'/fields'; +} + +function fieldgroup_remove_group(&$form_state, $bundle, $group_name) { + $groups = fieldgroup_groups($bundle); + $group = isset($groups[$group_name]) ? $groups[$group_name] : ''; + + if (empty($group)) { + drupal_not_found(); + exit; + } + + $form['#submit'][] = 'fieldgroup_remove_group_submit'; + $form['#bundle'] = $bundle; + $form['#group_name'] = $group_name; + + return confirm_form($form, + t('Are you sure you want to remove the group %label?', + array('%label' => t($group['label']))), + cck_bundle_admin_path($bundle) .'/fields', t('This action cannot be undone.'), + t('Remove'), t('Cancel')); +} + +function fieldgroup_remove_group_submit($form, &$form_state) { + $form_values = $form_state['values']; + $bundle = $form['#bundle']; + $group_name = $form['#group_name']; + fieldgroup_delete($bundle, $group_name); + drupal_set_message(t('The group %group_name has been removed.', array('%group_name' => $group_name))); + $form_state['redirect'] = cck_bundle_admin_path($bundle) .'/fields'; +} + +/* + * Returns all groups for a content type + */ +function fieldgroup_groups($bundle = '', $sorted = FALSE, $reset = FALSE) { + static $groups, $groups_sorted; + if (!isset($groups) || $reset) { + if ($cached = cache_get('fieldgroup_data', 'cache_field')) { + $data = $cached->data; + $groups = $data['groups']; + $groups_sorted = $data['groups_sorted']; + } + else { + $result = db_query("SELECT * FROM {field_group} ORDER BY weight, group_name"); + $groups = array(); + $groups_sorted = array(); + while ($group = db_fetch_array($result)) { + $group['settings'] = unserialize($group['settings']); + $group['fields'] = array(); + $groups[$group['bundle']][$group['group_name']] = $group; + $groups_sorted[$group['bundle']][] = &$groups[$group['bundle']][$group['group_name']]; + } + //load fields + $result = db_query("SELECT nfi.*, ng.group_name FROM {field_group} ng ". + "INNER JOIN {field_group_fields} ngf ON ngf.bundle = ng.bundle AND ngf.group_name = ng.group_name ". + "INNER JOIN {field_config_instance} nfi ON nfi.field_name = ngf.field_name AND nfi.bundle = ngf.bundle ". + "WHERE nfi.widget_active = 1 ORDER BY nfi.weight"); + while ($field = db_fetch_array($result)) { + $groups[$field['bundle']][$field['group_name']]['fields'][$field['field_name']] = $field; + } + cache_set('fieldgroup_data', array('groups' => $groups, 'groups_sorted' => $groups_sorted), 'cache_field'); + } + } + if (empty($bundle)) { + return $groups; + } + elseif (empty($groups) || empty($groups[$bundle])) { + return array(); + } + return $sorted ? $groups_sorted[$bundle] : $groups[$bundle]; +} + + +function _fieldgroup_groups_label($bundle) { + $groups = fieldgroup_groups($bundle); + + $labels[''] = '<'. t('none') .'>'; + foreach ($groups as $group_name => $group) { + $labels[$group_name] = t($group['label']); + } + return $labels; +} + +function _fieldgroup_field_get_group($bundle, $field_name) { + return db_result(db_query("SELECT group_name FROM {field_group_fields} WHERE bundle = '%s' AND field_name = '%s'", $bundle, $field_name)); +} + +/** + * Implementation of hook_form_alter() + */ +function fieldgroup_form_alter(&$form, $form_state, $form_id) { + // TODO This only works right on a node form, need to make it more general. + if (!empty($form['#fields']) && !empty($form['type'])) { + foreach (fieldgroup_groups($form['type']['#value']) as $group_name => $group) { + $form[$group_name] = array( + '#type' => 'fieldset', + '#title' => check_plain(t($group['label'])), + '#collapsed' => $group['settings']['form']['style'] == 'fieldset_collapsed', + '#collapsible' => in_array($group['settings']['form']['style'], array('fieldset_collapsed', 'fieldset_collapsible')), + '#weight' => $group['weight'], + '#description' => field_filter_xss(t($group['settings']['form']['description'])), + '#attributes' => array('class' => strtr($group['group_name'], '_', '-')), + ); + + $has_accessible_field = FALSE; + foreach ($group['fields'] as $field_name => $field) { + if (isset($form[$field_name])) { + // Track whether this group has any accessible fields within it. + if (!isset($form[$field_name]['#access']) || $form[$field_name]['#access'] !== FALSE) { + $has_accessible_field = TRUE; + } + // Move the form element. + $form[$group_name][$field_name] = $form[$field_name]; + unset($form[$field_name]); + $form['#fields'][$field_name]['form_path'] = array($group_name, $field_name); + } + } + if (!empty($group['fields']) && !element_children($form[$group_name])) { + //hide the fieldgroup, because the fields are hidden too + unset($form[$group_name]); + } + + if (!$has_accessible_field) { + // Hide the fieldgroup, because the fields are inaccessible. + $form[$group_name]['#access'] = FALSE; + } + + // Allow other modules to alter the form. + // Can't use module_invoke_all because we want + // to be able to use a reference to $form and $form_state. + foreach (module_implements('fieldgroup_form') as $module) { + $function = $module .'_fieldgroup_form'; + $function($form, $form_state, $form_id, $group); + } + + } + + } + // The group is only added here so it will appear in the export + // when using Content Copy. + elseif ($form_id == 'cck_field_edit_form' && isset($form['widget'])) { + $bundle = $form['bundle']['#value']; + $form['widget']['group'] = array( + '#type' => 'value', + '#value' => _fieldgroup_field_get_group($bundle, $form['field_name']['#value']), + ); + } + elseif ($form_id == 'cck_field_overview_form') { + $form['#validate'][] = 'fieldgroup_field_overview_form_validate'; + $form['#submit'][] = 'fieldgroup_field_overview_form_submit'; + } + elseif ($form_id == 'cck_display_overview_form' && !empty($form['#groups'])) { + $form['#submit'][] = 'fieldgroup_display_overview_form_submit'; + if (!isset($form['submit'])) { + $form['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#weight' => 10); + } + } + elseif ($form_id == 'cck_field_remove_form') { + $form['#submit'][] = 'fieldgroup_field_remove_form_submit'; + } +} + +/** + * API for group name validation. + * + * Pulled into separate function to be re-usable. + */ +function fieldgroup_validate_name($group, $bundle) { + $errors = array(); + + // No label. + if (!$group['label']) { + $errors['label'][] = t('You need to provide a label.'); + } + + // No group name. + if (!$group['group_name']) { + $errors['group_name'][] = t('You need to provide a group name.'); + } + // Group name validation. + else { + $group_name = $group['group_name']; + $group['group_type'] = !empty($group['group_type']) ? $group['group_type'] : 'standard'; + + // Add the 'group_' prefix. + if (substr($group_name, 0, 6) != 'group_') { + $group_name = 'group_'. $group_name; + } + + // Invalid field name. + if (!preg_match('!^group_[a-z0-9_]+$!', $group_name)) { + $errors['group_name'][] = t('The group name %group_name is invalid. The name must include only lowercase unaccentuated letters, numbers, and underscores.', array('%group_name' => $group_name)); + } + if (strlen($group_name) > 32) { + $errors['group_name'][] = t('The group name %group_name is too long. The name is limited to 32 characters, including the \'group_\' prefix.', array('%group_name' => $group_name)); + } + + // Group name already exists. + $groups = fieldgroup_groups($bundle); + if (isset($groups[$group_name])) { + $errors['group_name'][] = t('The group name %group_name already exists.', array('%group_name' => $group_name)); + } + if (empty($errors['group_name'])) { + $group['group_name'] = $group_name; + } + } + return array('group_name' => $group['group_name'], 'errors' => $errors); +} + +function fieldgroup_field_overview_form_validate($form, &$form_state) { + $form_values = $form_state['values']; + $group = $form_values['_add_new_group']; + + if (array_filter(array($group['label'], $group['group_name']))) { + $validation = fieldgroup_validate_name($group, $form['#bundle']); + if (!empty($validation['errors'])) { + foreach ($validation['errors'] as $type => $messages) { + foreach ($messages as $message) { + if ($type == 'label') { + form_set_error('_add_new_group][label', t('Add new group:') .' '. $message); + } + else { + form_set_error('_add_new_group][group_name', t('Add new group:') .' '. $message); + } + } + } + } + $group_name = $validation['group_name']; + form_set_value($form['_add_new_group']['group_name'], $group_name, $form_state); + } + else { + // Fail validation if attempt to nest fields under a new group without the + // proper information. Not raising an error would cause the nested fields + // to get weights the user doesn't expect. + + foreach ($form_values as $key => $values) { + if ($values['parent'] == '_add_new_group') { + form_set_error('_add_new_group][label', t('Add new group: you need to provide a label.')); + form_set_error('_add_new_group][group_name', t('Add new group: you need to provide a group name.')); + break; + } + } + } +} + +function fieldgroup_field_overview_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $bundle = $form['#bundle']; + + // Create new group if needed. + if (!empty($form_values['_add_new_group']['label'])) { + $group = $form_values['_add_new_group']; + $group['settings'] = field_group_default_settings($group['group_type']); + fieldgroup_save_group($bundle, $group); + $new_group_name = $group['group_name']; + } + + // Parse incoming rows. + $add_field_rows = array('_add_new_field', '_add_existing_field'); + $field_rows = array_merge($form['#fields'], $add_field_rows); + foreach ($form_values as $key => $values) { + // If 'field' row: update field parenting. + if (in_array($key, $field_rows)) { + // If newly added fields were added to a group: + if (in_array($key, $add_field_rows)) { + // We replace the '_add_*_field' key with the actual name of + // the field that got added. + // field_field_overview_form_submit() placed those + // in $form_state['fields_added'] for us. + if (isset($form_state['fields_added'][$key])) { + $key = $form_state['fields_added'][$key]; + } + else { + // No field was actually created : skip to next row. + continue; + } + } + // If the field was added to the newly created group, replace the + // '_add_new_group' value with the actual name of the group. + $parent = ($values['parent'] == '_add_new_group' && isset($new_group_name)) ? $new_group_name : $values['parent']; + // TODO: check the parent group does exist ? + fieldgroup_update_fields(array('field_name' => $key, 'group' => $parent, 'bundle' => $bundle)); + } + + // If 'group' row: update groups weights + // (possible newly created group has already been taken care of). + elseif (in_array($key, $form['#groups'])) { + db_query("UPDATE {field_group} SET weight = %d WHERE bundle = '%s' AND group_name = '%s'", + $values['weight'], $bundle, $key); + } + } + + cache_clear_all('fieldgroup_data', 'cache_field'); +} + +function field_group_default_settings($group_type) { + $settings = array( + 'form' => array('style' => 'fieldset', 'description' => ''), + 'display' => array('description' => '', 'label' => 'above'), + ); + module_load_include('inc', 'field', 'includes/field.admin'); + foreach (array_keys(field_build_modes(field_info_bundle_entity($bundle))) as $key) { + $settings['display'][$key]['format'] = 'fieldset'; + $settings['display'][$key]['exclude'] = 0; + } + // Allow other modules to add new default settings. + $settings = array_merge($settings, module_invoke_all('fieldgroup_default_settings', $group_type)); + return $settings; +} + +function fieldgroup_display_overview_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $groups = fieldgroup_groups($form['#bundle']); + foreach ($form_values as $key => $values) { + if (in_array($key, $form['#groups'])) { + $group = $groups[$key]; + // We have some numeric keys here, so we can't use array_merge. + $group['settings']['display'] = $values + $group['settings']['display']; + fieldgroup_save_group($form['#bundle'], $group); + } + } +} + +function fieldgroup_field_remove_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + // TODO: + // - when a (non last) field is removed from a group, a 'ghost row' remains in the fields overview + // - when the last field is removed, the group disappears + // seems to be fixed when emptying the cache. + db_query("DELETE FROM {". fieldgroup_fields_tablename() ."} WHERE bundle = '%s' AND field_name = '%s'", $form_values['bundle'], $form_values['field_name']); +} + +/** + * Implementation of hook_nodeapi(). + */ +function fieldgroup_nodeapi(&$node, $op, $teaser, $page) { + // TODO This will only work on a node, need to make it general enough to work on users. + switch ($op) { + case 'view': + // Prevent against invalid 'nodes' built by broken 3rd party code. + if (isset($node->type)) { + // NODE_BUILD_NORMAL is 0, and ('whatever' == 0) is TRUE, so we need a ===. + if ($node->build_mode === NODE_BUILD_NORMAL || $node->build_mode == NODE_BUILD_PREVIEW) { + $context = $teaser ? 'teaser' : 'full'; + } + else { + $context = $node->build_mode; + } + + foreach (fieldgroup_groups($node->type) as $group_name => $group) { + // Do not include group labels when indexing content. + if ($context == NODE_BUILD_SEARCH_INDEX) { + $group['settings']['display']['label'] = 'hidden'; + } + $label = $group['settings']['display']['label'] == 'above'; + $element = array( + '#title' => $label ? check_plain(t($group['label'])) : '', + '#description' => $label ? field_filter_xss(t($group['settings']['display']['description'])) : '', + ); + $format = isset($group['settings']['display'][$context]['format']) ? $group['settings']['display'][$context]['format'] : 'fieldset'; + + switch ($format) { + case 'simple': + $element['#type'] = 'fieldgroup_simple'; + $element['#group_name'] = $group_name; + break; + case 'hidden': + $element['#access'] = FALSE; + break; + + case 'fieldset_collapsed': + $element['#collapsed'] = TRUE; + case 'fieldset_collapsible': + $element['#collapsible'] = TRUE; + case 'fieldset': + $element['#type'] = 'fieldgroup_fieldset'; + $element['#attributes'] = array('class' => 'fieldgroup '. strtr($group['group_name'], '_', '-')); + break; + } + foreach ($group['fields'] as $field_name => $field) { + if (isset($node->content[$field_name])) { + $element[$field_name] = $node->content[$field_name]; + } + } + + // Allow other modules to alter the group view. + // Can't use module_invoke_all because we want + // to be able to use a reference to $node and $element. + foreach (module_implements('fieldgroup_view') as $module) { + $function = $module .'_fieldgroup_view'; + $function($node, $element, $group, $context); + } + + foreach ($group['fields'] as $field_name => $field) { + if (isset($node->content[$field_name])) { + unset($node->content[$field_name]); + } + } + + // The wrapper lets us get the themed output for the group + // to populate the $GROUP_NAME_rendered variable for node templates, + // and hide it from the $content variable if needed. + // See fieldgroup_preprocess_node(), theme_fieldgroup_wrapper(). + $wrapper = array( + 'group' => $element, + '#weight' => $group['weight'], + '#post_render' => array('fieldgroup_wrapper_post_render'), + '#group_name' => $group_name, + '#bundle' => $node->type, + '#context' => $context, + ); + + $node->content[$group_name] = $wrapper; + } + } + break; + } +} + +/** + * Hide specified fields from the $content variable in node templates. + */ +function fieldgroup_wrapper_post_render($content, $element) { + $groups = fieldgroup_groups($element['#bundle']); + $group = $groups[$element['#group_name']]; + + // The display settings are not in quite the same place in the + // group and the field, so create the value the theme will expect. + $group['display_settings'] = $group['settings']['display']; + if (theme('field_exclude', $content, $group, $element['#context'])) { + return ''; + } + return $content; +} + +/* + * Get the group name for a field. + * If the field isn't in a group, FALSE will be returned. + * @return The name of the group, or FALSE. + */ +function fieldgroup_get_group($bundle, $field_name) { + foreach (fieldgroup_groups($bundle) as $group_name => $group) { + if (in_array($field_name, array_keys($group['fields']))) { + return $group_name; + } + } + return FALSE; +} + +/** + * Implementation of hook_node_type() + * React to change in node types + */ +function fieldgroup_node_type($op, $info) { + if ($op == 'update' && !empty($info->old_type) && $info->type != $info->old_type) { + // update the tables + db_query("UPDATE {field_group} SET bundle='%s' WHERE bundle='%s'", array($info->type, $info->old_type)); + db_query("UPDATE {field_group_fields} SET bundle='%s' WHERE bundle='%s'", array($info->type, $info->old_type)); + cache_clear_all('fieldgroup_data', 'cache_field'); + } + elseif ($op == 'delete') { + db_query("DELETE FROM {field_group} WHERE bundle = '%s'", $info->type); + db_query("DELETE FROM {field_group_fields} WHERE bundle = '%s'", $info->type); + } +} + +function fieldgroup_types() { + $types = array('standard' => t('Standard group')); + // Allow other modules to add new group_types. + $types = array_merge($types, module_invoke_all('fieldgroup_types')); + return $types; +} + +/** + * CRUD API for fieldgroup module. + * + * @todo + * Make this into more of a real API for groups. + */ +/* + * Saves the given group for this content-type + */ +function fieldgroup_save_group($bundle, $group) { + $groups = fieldgroup_groups($bundle); + + // Allow other modules to intervene when the group is saved. + foreach (module_implements('fieldgroup_save_group') as $module) { + $function = $module .'_fieldgroup_save_group'; + $function($group); + } + + if (!isset($groups[$group['group_name']])) { + // Accept group name from programmed submissions if valid. + db_query("INSERT INTO {field_group} (group_type, bundle, group_name, label, settings, weight)". + " VALUES ('%s', '%s', '%s', '%s', '%s', %d)", $group['group_type'], $bundle, $group['group_name'], $group['label'], serialize($group['settings']), $group['weight']); + cache_clear_all('fieldgroup_data', 'cache_field'); + return SAVED_NEW; + } + else { + db_query("UPDATE {field_group} SET group_type = '%s', label = '%s', settings = '%s', weight = %d ". + "WHERE bundle = '%s' AND group_name = '%s'", + $group['group_type'], $group['label'], serialize($group['settings']), $group['weight'], $bundle, $group['group_name']); + cache_clear_all('fieldgroup_data', 'cache_field'); + return SAVED_UPDATED; + } +} + +function fieldgroup_update_fields($form_values) { + $default = _fieldgroup_field_get_group($form_values['bundle'], $form_values['field_name']); + + if ($default != $form_values['group']) { + if ($form_values['group'] && !$default) { + db_query("INSERT INTO {field_group_fields} (bundle, group_name, field_name) VALUES ('%s', '%s', '%s')", $form_values['bundle'], $form_values['group'], $form_values['field_name']); + } + elseif ($form_values['group']) { + db_query("UPDATE {field_group_fields} SET group_name = '%s' WHERE bundle = '%s' AND field_name = '%s'", $form_values['group'], $form_values['bundle'], $form_values['field_name']); + } + else { + db_query("DELETE FROM {field_group_fields} WHERE bundle = '%s' AND field_name = '%s'", $form_values['bundle'], $form_values['field_name']); + } + cache_clear_all('fieldgroup_data', 'cache_field'); + } +} + +function fieldgroup_delete($bundle, $group_name) { + db_query("DELETE FROM {field_group} WHERE bundle = '%s' AND group_name = '%s'", $bundle, $group_name); + db_query("DELETE FROM {field_group_fields} WHERE bundle = '%s' AND group_name = '%s'", $bundle, $group_name); + cache_clear_all('fieldgroup_data', 'cache_field'); +} + +/** + * Format a fieldgroup using a 'fieldset'. + * + * Derived from core's theme_fieldset, with no output if the content is empty. + */ +function theme_fieldgroup_fieldset($element) { + if (empty($element['#children']) && empty($element['#value'])) { + return ''; + } + + if ($element['#collapsible']) { + drupal_add_js('misc/collapse.js'); + + if (!isset($element['#attributes']['class'])) { + $element['#attributes']['class'] = ''; + } + + $element['#attributes']['class'] .= ' collapsible'; + if ($element['#collapsed']) { + $element['#attributes']['class'] .= ' collapsed'; + } + } + return ''. ($element['#title'] ? ''. $element['#title'] .'' : '') . (isset($element['#description']) && $element['#description'] ? '
'. $element['#description'] .'
' : '') . (!empty($element['#children']) ? $element['#children'] : '') . (isset($element['#value']) ? $element['#value'] : '') ."\n"; +} + + +/** + * Process variables for fieldgroup.tpl.php. + * + * The $variables array contains the following arguments: + * - $group_name + * - $group_name_css + * - $label + * - $description + * - $content + * + * @see fieldgroup.tpl.php + */ +function fieldgroup_preprocess_fieldgroup_simple(&$vars) { + $element = $vars['element']; + + $vars['group_name'] = $element['#group_name']; + $vars['group_name_css'] = strtr($element['#group_name'], '_', '-'); + $vars['label'] = isset($element['#title']) ? $element['#title'] : '';; + $vars['description'] = isset($element['#description']) ? $element['#description'] : '';; + $vars['content'] = isset($element['#children']) ? $element['#children'] : ''; +} + +/** + * Theme preprocess function for node. + * + * Adds $GROUP_NAME_rendered variables, + * containing the themed output for the whole group. + */ +function fieldgroup_preprocess_node(&$vars) { + $node = $vars['node']; + + foreach (fieldgroup_groups($node->type) as $group_name => $group) { + // '#chilren' might not be set if the group is empty. + $vars[$group_name .'_rendered'] = isset($node->content[$group_name]['#children']) ? $node->content[$group_name]['#children'] : ''; + } +} \ No newline at end of file === added file 'sites/all/modules/cck/modules/fieldgroup/fieldgroup.tpl.php' --- sites/all/modules/cck/modules/fieldgroup/fieldgroup.tpl.php 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/modules/fieldgroup/fieldgroup.tpl.php 2009-01-15 16:50:16 +0000 @@ -0,0 +1,33 @@ + + +
+ + +

+ + +
+ + + + +
+ +
+ === added directory 'sites/all/modules/cck/theme' === added file 'sites/all/modules/cck/theme/cck-admin-display-overview-form.tpl.php' --- sites/all/modules/cck/theme/cck-admin-display-overview-form.tpl.php 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/theme/cck-admin-display-overview-form.tpl.php 2009-01-15 16:50:16 +0000 @@ -0,0 +1,42 @@ + +
+ +
+ + + + + + $value): ?> + + + + $value): ?> + + + + + + + + + + + $title): ?> + + + + + + + +
  + +
indentation; ?>human_name; ?>{$context}->label; ?>{$context}->type; ?>{$context}->exclude; ?>
+ + === added file 'sites/all/modules/cck/theme/cck-admin-field-overview-form.tpl.php' --- sites/all/modules/cck/theme/cck-admin-field-overview-form.tpl.php 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/theme/cck-admin-field-overview-form.tpl.php 2009-01-15 16:50:15 +0000 @@ -0,0 +1,103 @@ + +
+ +
+ + + + + + + + + + + + + + + row_type): + case 'field': ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ indentation; ?> + label; ?> + weight . $row->parent . $row->hidden_name; ?>field_name; ?>type; ?>widget_type; ?>configure; ?>  remove; ?> + indentation; ?> + label; ?> + weight . $row->parent . $row->hidden_name; ?>group_name; ?>group_type; ?>configure; ?>  remove; ?> + indentation; ?> + label; ?> + weight . $row->parent . $row->hidden_name; ?>description; ?> + indentation; ?> +
+
+ label; ?> +
+
 
weight . $row->parent . $row->hidden_name; ?>
 
field_name; ?>
 
type; ?>
 
widget_type; ?>
+ indentation; ?> +
+
+ label; ?> +
+
 
weight . $row->parent . $row->hidden_name; ?>
 
field_name; ?>
 
widget_type; ?>
+ indentation; ?> +
+
+ label; ?> +
+
 
weight . $row->parent . $row->hidden_name; ?>
 
group_name; ?>
 
group_type; ?>
 
group_option; ?>
+ + + === added file 'sites/all/modules/cck/theme/cck.css' --- sites/all/modules/cck/theme/cck.css 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/theme/cck.css 2009-01-15 16:50:16 +0000 @@ -0,0 +1,28 @@ +/* $Id$ */ + +/* 'Manage fields' overview */ +#cck-field-overview-form .advanced-help-link, +#cck-display-overview-form .advanced-help-link { + margin: 4px 4px 0 0; +} +#cck-field-overview-form .label-group, +#cck-display-overview-form .label-group, +#cck-copy-export-form .label-group { + font-weight: bold; +} +table#cck-field-overview .label-add-new-field, +table#cck-field-overview .label-add-existing-field, +table#cck-field-overview .label-add-new-group { + float: left; +} +table#cck-field-overview tr.cck-add-new .tabledrag-changed { + display: none; +} +table#cck-field-overview tr.cck-add-new .description { + margin-bottom: 0; +} +table#cck-field-overview .cck-new { + font-weight: bold; + padding-bottom: .5em; +} + === added file 'sites/all/modules/cck/theme/theme.inc' --- sites/all/modules/cck/theme/theme.inc 1970-01-01 00:00:00 +0000 +++ sites/all/modules/cck/theme/theme.inc 2009-01-15 16:50:16 +0000 @@ -0,0 +1,164 @@ +'. t('You can add a field to a group by dragging it below and to the right of the group.'); + } + if (!module_exists('advanced_help')) { + //$vars['help'] .= '
' . t('Note: Installing the Advanced help module will let you access more and better help.', array('!adv_help' => 'http://drupal.org/project/advanced_help')); + } + + $order = _cck_overview_order($form, $form['#field_rows'], $form['#group_rows']); + $rows = array(); + + // Identify the 'new item' keys in the form, they look like + // _add_new_field, add_new_group. + $keys = array_keys($form); + $add_rows = array(); + foreach ($keys as $key) { + if (substr($key, 0, 4) == '_add') { + $add_rows[] = $key; + } + } + while ($order) { + $key = reset($order); + $element = &$form[$key]; + + // Only display the 'Add' separator if the 'add' rows are still + // at the end of the table. + if (!isset($added_separator)) { + $remaining_rows = array_diff($order, $add_rows); + if (empty($remaining_rows) && empty($element['#depth'])) { + $row = new stdClass(); + $row->row_type = 'separator'; + $row->class = 'tabledrag-leaf region'; + $rows[] = $row; + $added_separator = TRUE; + } + } + + $row = new stdClass(); + + // Add target classes for the tabledrag behavior. + $element['weight']['#attributes']['class'] = 'field-weight'; + $element['parent']['#attributes']['class'] = 'group-parent'; + $element['hidden_name']['#attributes']['class'] = 'field-name'; + // Add target classes for the update selects behavior. + switch ($element['#row_type']) { + case 'add_new_field': + $element['type']['#attributes']['class'] = 'cck-field-type-select'; + $element['widget_type']['#attributes']['class'] = 'cck-widget-type-select'; + break; + case 'add_existing_field': + $element['field_name']['#attributes']['class'] = 'cck-field-select'; + $element['widget_type']['#attributes']['class'] = 'cck-widget-type-select'; + $element['label']['#attributes']['class'] = 'cck-label-textfield'; + break; + } + foreach (element_children($element) as $child) { + $row->{$child} = drupal_render($element[$child]); + } + $row->label_class = 'label-'. strtr($element['#row_type'], '_', '-'); + $row->row_type = $element['#row_type']; + $row->indentation = theme('indentation', isset($element['#depth']) ? $element['#depth'] : 0); + $row->class = 'draggable'; + $row->class .= isset($element['#disabled_row']) ? ' menu-disabled' : ''; + $row->class .= isset($element['#add_new']) ? ' cck-add-new' : ''; + $row->class .= isset($element['#leaf']) ? ' tabledrag-leaf' : ''; + $row->class .= isset($element['#root']) ? ' tabledrag-root' : ''; + + $rows[] = $row; + array_shift($order); + } + $vars['rows'] = $rows; + $vars['submit'] = drupal_render($form); + + // Add tabledrag behavior. +// drupal_add_tabledrag('cck-field-overview', 'match', 'parent', 'group-parent', 'group-parent', 'field-name', FALSE, 1); + drupal_add_tabledrag('cck-field-overview', 'match', 'parent', 'group-parent', 'group-parent', 'field-name', TRUE, 1); +// drupal_add_tabledrag('cck-field-overview', 'order', 'sibling', 'field-weight', NULL, NULL, FALSE); + drupal_add_tabledrag('cck-field-overview', 'order', 'sibling', 'field-weight'); + + // Add settings for the update selects behavior. + // TODO D7 : adapt... + $js_fields = array(); + foreach (cck_existing_field_options($form['#bundle']) as $field_name => $fields) { + $field = field_info_field($field_name); + $instance = field_info_instance($field_name, $form['#bundle']); + $js_fields[$field_name] = array('label' => $instance['label'], 'type' => $field['type'], 'widget' => $instance['widget']['type']); + } + drupal_add_js(array('cckWidgetTypes' => cck_widget_type_options(), 'cckFields' => $js_fields), 'setting'); + drupal_add_js(drupal_get_path('module', 'cck') .'/cck.js'); +} + +/** + * Theme preprocess function for cck-admin-display-overview-form.tpl.php. + */ +function template_preprocess_cck_display_overview_form(&$vars) { + $form = &$vars['form']; + + $contexts_selector = $form['#contexts']; + $vars['basic'] = $contexts_selector == 'basic'; + + $vars['contexts'] = cck_build_modes(field_info_bundle_entity($form['#bundle']), $contexts_selector); + + switch ($form['#bundle']) { + case 'user': + $help = t("Configure how user fields and field labels should be displayed. Use the 'Exclude' checkbox to exclude an item from the !content value passed to the user template.", array('!content' => '$user_profile')); + break; + + default: + if ($contexts_selector == 'basic') { + $help = t("Configure how this content type's fields and field labels should be displayed when it's viewed in teaser and full-page mode."); + } + else { + $help = t("Configure how this content type's fields should be displayed when it's rendered in the following contexts."); + } + $help .= ' '. t("Use the 'Exclude' checkbox to exclude an item from the !content value passed to the node template.", array('!content' => '$content')); + } + + $vars['help'] = $help; + + $order = _cck_overview_order($form, $form['#fields'], $form['#groups']); + if (empty($order)) { + $vars['rows'] = array(); + $vars['submit'] = ''; + return; + } + $rows = array(); + foreach ($order as $key) { + $element = &$form[$key]; + $row = new stdClass(); + foreach (element_children($element) as $child) { + if (!array_key_exists('exclude', $element[$child])) { + $row->{$child} = drupal_render($element[$child]); + } + else { + $row->{$child}->label = drupal_render($element[$child]['label']); + $row->{$child}->type = drupal_render($element[$child]['type']); + $row->{$child}->exclude = drupal_render($element[$child]['exclude']); + } + } + $row->label_class = in_array($key, $form['#groups']) ? 'label-group' : 'label-field'; + $row->indentation = theme('indentation', isset($element['#depth']) ? $element['#depth'] : 0); + $rows[] = $row; + } + + $vars['rows'] = $rows; + $vars['submit'] = drupal_render($form); +} \ No newline at end of file === added directory 'sites/all/modules/demofield' === added file 'sites/all/modules/demofield/demofield.info' --- sites/all/modules/demofield/demofield.info 1970-01-01 00:00:00 +0000 +++ sites/all/modules/demofield/demofield.info 2009-01-15 16:50:16 +0000 @@ -0,0 +1,7 @@ +; $Id: devel.info,v 1.8 2008/06/27 01:06:53 weitzman Exp $ +name = Demo Field +description = Demo attaching a field to users and nodes +dependencies[] = field +dependencies[] = text +core = 7.x +files[] = demofield.module === added file 'sites/all/modules/demofield/demofield.install' --- sites/all/modules/demofield/demofield.install 1970-01-01 00:00:00 +0000 +++ sites/all/modules/demofield/demofield.install 2009-01-15 16:50:16 +0000 @@ -0,0 +1,41 @@ + $field_name, 'type' => 'text'); + field_create_field($field); + + // Create a field instance structure. + $instance = array( + 'field_name' => $field_name, + 'label' => 'Demofield Demo', + 'description' => 'This is a demonstration text field.'); + $instance['widget'] = array( + 'type' => 'text_textfield' + ); + $instance['display'] = array( + 'full' => array( + 'label' => 'above', + 'type' => 'text_default', + 'exclude' => 0) + ); + + // Attach the field to all users (because user.module does not + // define multiple "content types"). + $instance['bundle'] = 'user'; + field_create_instance($instance); + + // Attach the field to "articles" (a name defined by node.module). + $instance['bundle'] = 'article'; + field_create_instance($instance); +} + +function demofield_uninstall() { + // Field delete functions work but only leave data and tables marked + // as deleted. The garbage-collection process isn't implemented. + // So, for now, do it directly... + db_query('DROP TABLE {field_data_demofield}'); + db_delete('field_config')->condition('field_name', 'demofield'); + db_delete('field_config_instance')->condition('field_name', 'demofield'); +} === added file 'sites/all/modules/demofield/demofield.module' --- sites/all/modules/demofield/demofield.module 1970-01-01 00:00:00 +0000 +++ sites/all/modules/demofield/demofield.module 2009-01-15 16:50:16 +0000 @@ -0,0 +1,1 @@ + 'Materialized View demo', + 'page callback' => 'materialized_view_demo_page', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +function materialized_view_demo_page() { + return 'nothing'; +} +*/ + +function materialized_view_demo_materialized_view_info() { + $mviews = array(); + + $mviews[] = _materialized_view_demo_nodes(); + + return $mviews; +} + +function _materialized_view_demo_nodes() { + $mview = new MaterializedView('mv_published_nodes_by_title'); + + $mview->addSort(new MVNodeTitleColumn(), MV_SORT_ASCENDING); + $mview->addStaticFilter(new MVNodeStatusColumn(), new MVEqualityOperator(1)); + + return $mview; +} + +// This isn't used yet. +/* +function _materialized_view_demo_entity_tables() { + $mviews = array(); + + // TK get proper list of entities. + $entities = array( + 'story', + 'page', + ); + + foreach ($entities as $entity) { + $mview = new MaterializedView('mv_' . $entity); + + $mviews[] = $mview; + } + + return $mviews; +} +*/