From 607e9626d5af265b18e8319b156bb0fda3445cd4 Mon Sep 17 00:00:00 2001 From: Dries Buytaert Date: Tue, 3 Feb 2009 17:30:13 +0000 Subject: - Patch #361683by Barry, Yves, Karen, Moshe Weitzman, David Strauss, floriant, chx, David Rothstein: initial field API patch. More work to be done, but ... oh my! --- modules/simpletest/drupal_web_test_case.php | 56 ++- modules/simpletest/tests/field_test.info | 9 + modules/simpletest/tests/field_test.install | 75 ++++ modules/simpletest/tests/field_test.module | 514 ++++++++++++++++++++++++++++ 4 files changed, 653 insertions(+), 1 deletion(-) create mode 100644 modules/simpletest/tests/field_test.info create mode 100644 modules/simpletest/tests/field_test.install create mode 100644 modules/simpletest/tests/field_test.module (limited to 'modules/simpletest') diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index 6d1086bef..5bb27dc2a 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -829,7 +829,7 @@ class DrupalWebTestCase { // 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 @@ -1996,4 +1996,58 @@ class DrupalWebTestCase { $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; + } } + diff --git a/modules/simpletest/tests/field_test.info b/modules/simpletest/tests/field_test.info new file mode 100644 index 000000000..3dd8f3fa1 --- /dev/null +++ b/modules/simpletest/tests/field_test.info @@ -0,0 +1,9 @@ +;$Id$ +name = "Field API Test" +description = "Support module for the Field API tests." +core = 7.x +package = testing +files[] = field_test.module +files[] = field_test.install +version = VERSION +hidden = TRUE diff --git a/modules/simpletest/tests/field_test.install b/modules/simpletest/tests/field_test.install new file mode 100644 index 000000000..c706ebfe4 --- /dev/null +++ b/modules/simpletest/tests/field_test.install @@ -0,0 +1,75 @@ + 'The base table for test_entities.', + 'fields' => array( + 'ftid' => array( + 'description' => 'The primary identifier for a test_entity.', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'ftvid' => array( + 'description' => 'The current {test_entity_revision}.ftvid version identifier.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'fttype' => array( + 'description' => 'The type of this test_entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'unique keys' => array( + 'ftvid' => array('ftvid'), + ), + 'primary key' => array('ftid'), + ); + $schema['test_entity_revision'] = array( + 'description' => 'Stores information about each saved version of a {test_entity}.', + 'fields' => array( + 'ftid' => array( + 'description' => 'The {test_entity} this version belongs to.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'ftvid' => array( + 'description' => 'The primary identifier for this version.', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + ), + 'indexes' => array( + 'nid' => array('ftid'), + ), + 'primary key' => array('ftvid'), + ); + + return $schema; +} + +/** + * Implementation of hook_install(). + */ +function field_test_install() { + drupal_install_schema('field_test'); +} + +/** + * Implementation of hook_uninstall(). + */ +function field_test_uninstall() { + drupal_uninstall_schema('field_test'); +} diff --git a/modules/simpletest/tests/field_test.module b/modules/simpletest/tests/field_test.module new file mode 100644 index 000000000..230eb7a54 --- /dev/null +++ b/modules/simpletest/tests/field_test.module @@ -0,0 +1,514 @@ + array( + 'title' => t('Access field_test content'), + 'description' => t('View published field_test content.'), + ), + 'administer field_test content' => array( + 'title' => t('Administer field_test content'), + 'description' => t('Manage field_test content'), + ), + ); + return $perms; +} + +/** + * Implementation of hook_menu(). + */ +function field_test_menu() { + $items = array(); + $info = field_test_fieldable_info(); + + foreach (array_keys($info['test_entity']['bundles']) as $bundle) { + $bundle_url_str = str_replace('_', '-', $bundle); + $items['test-entity/add/' . $bundle_url_str] = array( + 'title' => "Add $bundle test_entity", + 'page callback' => 'field_test_entity_add', + 'page arguments' => array(2), + 'access arguments' => array('administer field_test content'), + 'type' => MENU_NORMAL_ITEM, + ); + } + $items['test-entity/%field_test_entity/edit'] = array( + 'title' => 'Edit test entity', + 'page callback' => 'field_test_entity_edit', + 'page arguments' => array(1), + 'access arguments' => array('administer field_test content'), + 'type' => MENU_NORMAL_ITEM, + ); + + return $items; +} + + +/** + * + * 'Field attach' API. + * + */ + + +/** + * Define a test fieldable entity. + */ +function field_test_fieldable_info() { + $bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle')); + return array( + 'test_entity' => array( + 'name' => t('Test Entity'), + 'id key' => 'ftid', + 'revision key' => 'ftvid', + 'cacheable' => FALSE, + 'bundle key' => 'fttype', + 'bundles' => $bundles, + ), + // This entity type doesn't get form handling for now... + '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 == 'test_entity' || $obj_type == 'test_cacheable_entity') { + $modes = array( + 'full' => t('Full node'), + 'teaser' => t('Teaser'), + ); + } + return $modes; +} + +/** + * Helper function to create a basic 'test entity' structure. + * + * TODO : do we stil need this now that we can actualy load and save test_entities ? + */ +function field_test_create_stub_entity($id = 1, $vid = 1, $bundle = FIELD_TEST_BUNDLE) { + $entity = new stdClass(); + $entity->ftid = $id; + $entity->ftvid = $vid; + $entity->fttype = $bundle; + + return $entity; +} + +function field_test_entity_load($ftid, $ftvid = NULL) { + // Load basic strucure. + $query = db_select('test_entity', 'fte', array()) + ->fields('fte') + ->condition('ftid', $ftid); + if ($ftvid) { + $query->condition('ftvid', $ftvid); + } + $entities = $query->execute()->fetchAllAssoc('ftid'); + + // Attach fields. + if ($ftvid) { + field_attach_load_revision('test_entity', $entities); + } + else { + field_attach_load('test_entity', $entities); + } + + return $entities[$ftid]; +} + +function field_test_entity_save(&$entity) { + field_attach_presave('test_entity', $entity); + + $entity->is_new = FALSE; + if (empty($entity->ftid)) { + // Insert a new test_entity. + $entity->is_new = TRUE; + } + elseif (!empty($entity->revision)) { + $entity->old_ftvid = $entity->ftvid; + } + + $update_entity = TRUE; + if ($entity->is_new) { + drupal_write_record('test_entity', $entity); + drupal_write_record('test_entity_revision', $entity); + $op = 'insert'; + } + else { + drupal_write_record('test_entity', $entity, 'ftid'); + if (!empty($entity->revision)) { + drupal_write_record('test_entity_revision', $entity); + } + else { + drupal_write_record('test_entity_revision', $entity, 'ftvid'); + $update_entity = FALSE; + } + $op = 'update'; + } + if ($update_entity) { + db_update('test_entity') + ->fields(array('ftvid' => $entity->ftvid)) + ->condition('ftid', $entity->ftid) + ->execute(); + } + + // Save fields. + $function = "field_attach_$op"; + $function('test_entity', $entity); +} + +function field_test_entity_add($fttype) { + $fttype = str_replace('-', '_', $fttype); + $entity = (object)array('fttype' => $fttype); + drupal_set_title(t('Create test_entity @bundle', array('@bundle' => $fttype)), PASS_THROUGH); + return drupal_get_form('field_test_entity_form', $entity); +} + +function field_test_entity_edit($entity) { + drupal_set_title(t('test_entity @ftid revision @ftvid', array('@ftid' => $entity->ftid, '@ftvid' => $entity->ftvid)), PASS_THROUGH); + return drupal_get_form('field_test_entity_form', $entity); +} + +/** + * Form to set the value of fields attached to our entity. + */ +function field_test_entity_form(&$form_state, $entity) { + $form = array(); + + if (isset($form_state['test_entity'])) { + $entity = $form_state['test_entity'] + (array)$entity; + } + $entity = (object)$entity; + + foreach (array('ftid', 'ftvid', 'fttype') as $key) { + $form[$key] = array( + '#type' => 'value', + '#value' => isset($entity->$key) ? $entity->$key : NULL, + ); + } + + // Add field widgets. + $form['#builder_function'] = 'field_test_entity_form_submit_builder'; + field_attach_form('test_entity', $entity, $form, $form_state); + + $form['revision'] = array( + '#access' => user_access('administer field_test content'), + '#type' => 'checkbox', + '#title' => t('Create new revision'), + '#default_value' => FALSE, + '#weight' => 100, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#weight' => 101, + ); + + return $form; +} + +/** + * Validate handler for field_test_set_field_values(). + */ +function field_test_entity_form_validate($form, &$form_state) { + $entity = (object)$form_state['values']; + field_attach_validate('test_entity', $entity, $form); +} + +/** + * Submit handler for field_test_set_field_values(). + */ +function field_test_entity_form_submit($form, &$form_state) { + $entity = field_test_entity_form_submit_builder($form, $form_state); + $insert = empty($entity->ftid); + field_test_entity_save($entity); + + $message = $insert ? t('test_entity @id has been created.', array('@id' => $entity->ftid)) : t('test_entity @id has been updated.', array('@id' => $entity->ftid)); + drupal_set_message($message); + + if ($entity->ftid) { + unset($form_state['rebuild']); + $form_state['redirect'] = 'test-entity/' . $entity->ftid . '/edit'; + } + else { + // Error on save. + drupal_set_message(t('The entity could not be saved.'), 'error'); + } + +} + +/** + * Build a test_entity by processing submitted form values and prepare for a form rebuild. + */ +function field_test_entity_form_submit_builder($form, &$form_state) { + $entity = field_test_create_stub_entity($form_state['values']['ftid'], $form_state['values']['ftvid'], $form_state['values']['fttype']); + field_attach_submit('test_entity', $entity, $form, $form_state); + + $form_state['test_entity'] = (array)$entity; + $form_state['rebuild'] = TRUE; + + return $entity; +} + +/** + * + * 'Field type' API. + * + */ + +/** + * 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 does not accept the value -1.', array('%name' => $instance['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_multiple' => '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 $field + * The field structure. + * @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, $field, $instance, $items, $delta = 0) { + $element = array( + 'value' => array( + '#title' => $instance['label'], + '#type' => 'textfield', + '#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : '', + '#required' => $instance['required'], + ), + ); + 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, + ), + ), + ); +} + +/** + * 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), + ), + ); +} + +/** + * 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 -- cgit v1.2.3