diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/includes/database.mysql.inc ./includes/database.mysql.inc
--- ../head/includes/database.mysql.inc 2008-05-27 11:26:13.000000000 -0400
+++ ./includes/database.mysql.inc 2008-05-30 13:24:15.000000000 -0400
@@ -140,9 +140,9 @@ function _db_query($query, $debug = 0) {
* An object representing the next row of the result, or FALSE. The attributes
* of this object are the table fields selected by the query.
*/
-function db_fetch_object($result) {
+function db_fetch_object($result, $class = 'stdClass') {
if ($result) {
- return mysql_fetch_object($result);
+ return mysql_fetch_object($result, $class);
}
}
diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/includes/database.mysqli.inc ./includes/database.mysqli.inc
--- ../head/includes/database.mysqli.inc 2008-05-27 11:26:13.000000000 -0400
+++ ./includes/database.mysqli.inc 2008-05-30 13:33:14.000000000 -0400
@@ -141,9 +141,9 @@ function _db_query($query, $debug = 0) {
* An object representing the next row of the result, or FALSE. The attributes
* of this object are the table fields selected by the query.
*/
-function db_fetch_object($result) {
+function db_fetch_object($result, $class = 'stdClass') {
if ($result) {
- $object = mysqli_fetch_object($result);
+ $object = mysqli_fetch_object($result, $class);
return isset($object) ? $object : FALSE;
}
}
diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/includes/database.pgsql.inc ./includes/database.pgsql.inc
--- ../head/includes/database.pgsql.inc 2008-05-27 11:26:13.000000000 -0400
+++ ./includes/database.pgsql.inc 2008-05-30 17:37:27.000000000 -0400
@@ -170,9 +170,9 @@ function _db_query($query, $debug = 0) {
* An object representing the next row of the result, or FALSE. The attributes
* of this object are the table fields selected by the query.
*/
-function db_fetch_object($result) {
+function db_fetch_object($result, $class = 'stdClass') {
if ($result) {
- return pg_fetch_object($result);
+ return pg_fetch_object($result, NULL, $class);
}
}
diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/content.info ./modules/fields/content.info
--- /dev/null 1969-12-31 19:00:00.000000000 -0500
+++ ./modules/fields/content.info 2008-05-31 11:52:48.000000000 -0400
@@ -0,0 +1,7 @@
+name = Fields
+description = Fields in core. Whatever that means!
+package = Core - fields
+core = 7.x
+files[] = content.module
+files[] = includes/content.crud.inc
+
diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/content.install ./modules/fields/content.install
--- /dev/null 1969-12-31 19:00:00.000000000 -0500
+++ ./modules/fields/content.install 2008-05-31 11:52:48.000000000 -0400
@@ -0,0 +1,300 @@
+ $types) {
+ foreach ($types as $type_name => $type) {
+ if ($field['type'] == $type_name) {
+ $field['module'] = $module;
+ }
+ }
+ }
+ foreach ($module_widgets as $module => $types) {
+ foreach ($types as $type_name => $type) {
+ if ($field['widget_type'] == $type_name) {
+ $field['widget_module'] = $module;
+ }
+ }
+ }
+ if (!empty($field['module']) && !empty($field['widget_module'])) {
+ $field['widget_settings'] = unserialize($field['widget_settings']);
+ $field['display_settings'] = unserialize($field['display_settings']);
+ $field['columns'] = module_invoke($field['module'], 'field_settings', 'database columns', $field);
+ $fields[$field['type_name']][$field['field_name']] = $field;
+ }
+ }
+ return $fields;
+}
+
+/**
+ * Implementation of hook_install().
+ */
+function content_install() {
+ variable_set('content_schema_version', 6002);
+ drupal_install_schema('content');
+}
+
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function content_uninstall() {
+ variable_del('content_schema_version');
+ drupal_uninstall_schema('content');
+}
+
+/**
+ * Implementation of hook_enable().
+ */
+function content_enable() {
+ // Make sure old data is emptied out of the caches, since it
+ // may no longer be valid since the module was last enabled,
+ // especially if not all the same field modules are enabled
+ // as before. Especially needed during updates.
+ cache_clear_all('*', 'cache_content', TRUE);
+ content_clear_type_cache(TRUE);
+}
+
+/**
+ * Implementation of hook_disable().
+ */
+function content_disable() {
+ // Make sure old data is emptied out of the caches, since it
+ // may no longer be valid when the module is re-enabled.
+ cache_clear_all('*', 'cache_content', TRUE);
+ content_clear_type_cache(TRUE);
+}
+
+/**
+ * Implementation of hook_schema.
+ */
+function content_schema() {
+
+ // Static (meta) tables.
+
+ $schema['content_node_field'] = array(
+ 'fields' => array(
+ 'field_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
+ 'type' => array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''),
+ 'global_settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE),
+ 'required' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
+ 'multiple' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
+ 'shareable' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
+ 'db_storage' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 1),
+ 'module' => array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''),
+ 'db_columns' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE),
+ 'active' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
+ ),
+ 'primary key' => array('field_name'),
+ );
+ $schema['content_node_field_instance'] = array(
+ 'fields' => array(
+ 'field_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
+ 'type_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
+ 'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+ 'label' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+ 'widget_type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
+ 'widget_settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE),
+ 'display_settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE),
+ 'description' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE),
+ 'widget_module' => array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''),
+ 'widget_active' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
+ ),
+ 'primary key' => array('field_name', 'type_name'),
+ );
+ $schema['cache_content'] = drupal_get_schema_unprocessed('system', 'cache');
+
+ // When the module is first installed, the remaining code in the schema
+ // will create errors, since these tables have not yet been created.
+ // We don't need to create data tables on initial installation anyway
+ // since no fields have been created yet, so just return with this much
+ // of the schema.
+
+ if (!db_table_exists('content_node_field') || !db_table_exists('content_node_field_instance')) {
+ return $schema;
+ }
+
+ // Dynamic (data) tables.
+
+ // We can't use many helper functions here, like content_fields() or
+ // content_types() or we risk creating a fatal loop from circular
+ // logic when they call other functions that use this schema, so create
+ // the schema directly from a fresh query of the database.
+
+ // content_table_schema() and content_database_info() have no
+ // circular logic and are safe to use here.
+
+ $db_result = db_query("SELECT * FROM {". content_instance_tablename() ."} nfi ".
+ " LEFT JOIN {". content_field_tablename() ."} nf ON nf.field_name = nfi.field_name WHERE nf.active = 1 AND nfi.widget_active = 1");
+ while ($field = db_fetch_array($db_result)) {
+ // 'columns' is a reserved word in MySQL4, so our db column is named 'db_columns'.
+ $field['columns'] = unserialize($field['db_columns']);
+ unset($field['db_columns']);
+
+ $content_table = _content_tablename($field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
+ $field_table = _content_tablename($field['field_name'], CONTENT_DB_STORAGE_PER_FIELD);
+
+
+ // We always add a 'per content type' table for each content type that
+ // has fields.
+ if (!isset($schema[$content_table])) {
+ $schema[$content_table] = content_table_schema();
+ }
+
+ $base_schema = content_table_schema($field);
+ if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) {
+ // Per-field storage : add the 'per field' table if needed.
+ if (!isset($schema[$field_table])) {
+ $schema[$field_table] = $base_schema;
+ }
+ }
+ else {
+ // Per-type storage : merge the information for the field
+ // in the existing table.
+ $schema[$content_table]['fields'] = array_merge($schema[$content_table]['fields'], $base_schema['fields']);
+ $schema[$content_table]['content fields'] = array_merge($schema[$content_table]['content fields'], $base_schema['content fields']);
+ }
+ }
+ return $schema;
+}
+
+function content_update_last_removed() {
+ return 1008;
+}
+
+/**
+ * Add module name to fields table to make it easier to identify the fields to delete when a module
+ * is uninstalled.
+ *
+ * Needed because the value drops out of content_info() when module is disabled, so there
+ * is no other way to find the associated fields.
+ */
+function content_update_6000() {
+ $ret = array();
+ if (db_column_exists(content_field_tablename(), 'active')) {
+ return $ret;
+ }
+ db_add_field($ret, content_field_tablename(), 'module', array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''));
+ db_add_field($ret, content_field_tablename(), 'db_columns', array('type' => 'text', 'not null' => TRUE, 'default' => ''));
+ db_add_field($ret, content_field_tablename(), 'active', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
+ db_add_field($ret, content_instance_tablename(), 'widget_module', array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''));
+ db_add_field($ret, content_instance_tablename(), 'widget_active', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
+
+ // This will update the table for any modules enabled at this time.
+ foreach (module_list() as $module) {
+ content_associate_fields($module);
+ }
+
+ // Fix the cache_content schema
+ if (db_table_exists('cache_content')) {
+ db_drop_table($ret, 'cache_content');
+ }
+ db_create_table($ret, 'cache_content', drupal_get_schema_unprocessed('system', 'cache'));
+ variable_set('content_schema_version', 6000);
+
+ // The cache table had to be used to store data until this update ran,
+ // so clear cache out now that we're switching back to the cache_content table.
+ $ret[] = update_sql('DELETE FROM {cache}');
+
+ return $ret;
+}
+
+/**
+ * Rename node_field and node_field_instance tables.
+ *
+ * This is a carryover from when the data tables were renamed,
+ * postponed so we wouldn't create any more havoc than necessary
+ * until a major version change.
+ *
+ * Using 'content_node_field' instead of 'content_field'
+ * to avoid conflicts with field tables that will be prefixed
+ * with 'content_field'.
+ */
+function content_update_6001() {
+ $ret = array();
+ if (db_table_exists('content_node_field')) {
+ return $ret;
+ }
+ db_rename_table($ret, 'node_field', 'content_node_field');
+ db_rename_table($ret, 'node_field_instance', 'content_node_field_instance');
+ variable_set('content_schema_version', 6001);
+ content_clear_type_cache(TRUE);
+ return $ret;
+}
+
+/**
+ * Get rid of automatic per content tables for content types that have no fields.
+ * Switching to adding those tables only when needed.
+ */
+function content_update_6002() {
+ $ret = array();
+
+ $db_types = content_types_install();
+ $field_types = array();
+
+ $result = db_query("SELECT DISTINCT type_name FROM {". content_instance_tablename() ."}");
+ while ($type = db_fetch_array($result)) {
+ $field_types[] = $type['type_name'];
+ }
+
+ foreach ($db_types as $content_type => $content_info) {
+ if (!in_array($content_type, $field_types)) {
+ $table = _content_tablename($content_type, CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
+ if (db_table_exists($table)) {
+ db_drop_table($ret, $table);
+ }
+ }
+ }
+ variable_set('content_schema_version', 6002);
+ content_clear_type_cache(TRUE);
+ return $ret;
+}
+
+/**
+ * 'db_columns' column 1st got introduced as 'columns', which is forbidden in MySQL 4.
+ * This update function will only be useful for early testers...
+ */
+function content_update_6003() {
+ $ret = array();
+ if (db_column_exists('content_node_field', 'columns')) {
+ db_change_field($ret, 'content_node_field', 'columns', 'db_columns', array('type' => 'text', 'size' => 'medium', 'not null' => TRUE));
+ }
+ return $ret;
+}
\ No newline at end of file
diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/content.module ./modules/fields/content.module
--- /dev/null 1969-12-31 19:00:00.000000000 -0500
+++ ./modules/fields/content.module 2008-05-31 11:52:48.000000000 -0400
@@ -0,0 +1,1780 @@
+ array(
+ 'template' => 'field',
+ 'arguments' => array('element' => NULL),
+ 'path' => $path,
+ ),
+ 'content_view_multiple_field' => array(
+ 'arguments' => array('items' => NULL, 'field' => NULL, 'data' => NULL),
+ ),
+ 'content_multiple_values' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ );
+}
+
+/**
+ * Load data for a node type's fields.
+ *
+ * When loading one of the content.module nodes, we need to let each field handle
+ * its own loading. This can make for a number of queries in some cases, so we
+ * cache the loaded object structure and invalidate it during the update process.
+ */
+function content_load($node) {
+ $cid = 'content:'. $node->nid .':'. $node->vid;
+ if ($cached = cache_get($cid, content_cache_tablename())) {
+ return $cached->data;
+ }
+ else {
+ $default_additions = _content_field_invoke_default('load', $node);
+ if ($default_additions) {
+ foreach ($default_additions as $key => $value) {
+ $node->$key = $value;
+ }
+ }
+ $additions = _content_field_invoke('load', $node);
+ if ($additions) {
+ foreach ($additions as $key => $value) {
+ $node->$key = $value;
+ $default_additions[$key] = $value;
+ }
+ }
+ cache_set($cid, $default_additions, content_cache_tablename());
+ return $default_additions;
+ }
+}
+
+/**
+ * Nodeapi 'validate' op.
+ *
+ */
+function content_validate(&$node) {
+ _content_field_invoke('validate', $node);
+ _content_field_invoke_default('validate', $node);
+}
+
+/**
+ * Nodeapi 'presave' op.
+ *
+ */
+function content_presave(&$node) {
+ _content_field_invoke('presave', $node);
+ _content_field_invoke_default('presave', $node);
+}
+
+/**
+ * Nodeapi 'insert' op.
+ *
+ * Insert node type fields.
+ */
+function content_insert(&$node) {
+ _content_field_invoke('insert', $node);
+ _content_field_invoke_default('insert', $node);
+}
+
+/**
+ * Nodeapi 'update' op.
+ *
+ * Update node type fields.
+ */
+function content_update(&$node) {
+ _content_field_invoke('update', $node);
+ _content_field_invoke_default('update', $node);
+ cache_clear_all('content:'. $node->nid .':'. $node->vid, content_cache_tablename());
+}
+
+/**
+ * Nodeapi 'delete' op.
+ *
+ * Delete node type fields.
+ */
+function content_delete(&$node) {
+ $type = content_types($node->type);
+ if (!empty($type['fields'])) {
+ _content_field_invoke('delete', $node);
+ _content_field_invoke_default('delete', $node);
+ }
+ $table = _content_tablename($type['type'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
+ if (db_table_exists($table)) {
+ db_query('DELETE FROM {'. $table .'} WHERE nid = %d', $node->nid);
+ }
+ cache_clear_all('content:'. $node->nid, content_cache_tablename(), TRUE);
+}
+
+/**
+ * Nodeapi 'delete_revision' op.
+ *
+ * Delete node type fields for a revision.
+ */
+function content_delete_revision(&$node) {
+ $type = content_types($node->type);
+ if (!empty($type['fields'])) {
+ _content_field_invoke('delete revision', $node);
+ _content_field_invoke_default('delete revision', $node);
+ }
+ $table = _content_tablename($type['type'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
+ if (db_table_exists($table)) {
+ db_query('DELETE FROM {'. $table .'} WHERE vid = %d', $node->vid);
+ }
+ cache_clear_all('content:'. $node->nid .':'. $node->vid, content_cache_tablename());
+}
+
+/**
+ * Nodeapi 'view' op.
+ *
+ * Generate field render arrays.
+ */
+function content_view(&$node, $teaser = FALSE, $page = FALSE) {
+ // Let field modules sanitize their data for output.
+ _content_field_invoke('sanitize', $node, $teaser, $page);
+
+ // Merge fields.
+ $additions = _content_field_invoke_default('view', $node, $teaser, $page);
+ $node->content = array_merge((array) $node->content, $additions);
+
+ // Adjust weights for non-CCK fields.
+ $type = content_types($node->type);
+ foreach ($type['extra'] as $key => $value) {
+ // Some core 'fields' use a different key in node forms and in 'view'
+ // render arrays.
+ if (isset($value['view']) && isset($node->content[$value['view']])) {
+ $node->content[$value['view']]['#weight'] = $value['weight'];
+ }
+ elseif (isset($node->content[$key])) {
+ $node->content[$key]['#weight'] = $value['weight'];
+ }
+ }
+}
+
+/**
+ * Nodeapi 'alter' op.
+ *
+ * Add back the formatted values in the 'view' element for all fields,
+ * so that node templates can use it.
+ */
+function content_alter(&$node, $teaser = FALSE, $page = FALSE) {
+ _content_field_invoke_default('alter', $node, $teaser, $page);
+}
+
+/**
+ * Nodeapi 'prepare translation' op.
+ *
+ * Generate field render arrays.
+ */
+function content_prepare_translation(&$node) {
+ $additions = _content_field_invoke_default('prepare translation', $node);
+ $node = (object) array_merge((array) $node, $additions);
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ */
+function content_nodeapi(&$node, $op, $teaser, $page) {
+ switch ($op) {
+ case 'load':
+ return content_load($node);
+
+ case 'validate':
+ content_validate($node);
+ break;
+
+ case 'presave':
+ content_presave($node);
+ break;
+
+ case 'insert':
+ content_insert($node);
+ break;
+
+ case 'update':
+ content_update($node);
+ break;
+
+ case 'delete':
+ content_delete($node);
+ break;
+
+ case 'delete revision':
+ content_delete_revision($node);
+ break;
+
+ case 'view':
+ content_view($node, $teaser, $page);
+ break;
+
+ case 'alter':
+ content_alter($node, $teaser, $page);
+ break;
+
+ case 'prepare translation':
+ content_prepare_translation($node);
+ break;
+
+ }
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function content_form_alter(&$form, $form_state, $form_id) {
+ if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
+ module_load_include('inc', 'content', 'includes/content.node_form');
+ // Merge field widgets.
+ $form = array_merge($form, content_form($form, $form_state));
+ // Adjust weights for non-CCK fields.
+ $type = content_types($form['type']['#value']);
+ foreach ($type['extra'] as $key => $value) {
+ if (isset($form[$key])) {
+ $form[$key]['#weight'] = $value['weight'];
+ // Special case for the 'menu path' fieldset : we keep it
+ // just below the title.
+ if ($key == 'title' && isset($form['menu'])) {
+ $form['menu']['#weight'] = $value['weight'] + .1;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Theme an individual form element.
+ *
+ * Combine multiple values into a table with drag-n-drop reordering.
+ */
+function theme_content_multiple_values($element) {
+ $field_name = $element['#field_name'];
+ $field = content_fields($field_name);
+ $output = '';
+
+ if ($field['multiple'] >= 1) {
+ $table_id = $element['#field_name'] .'_values';
+ $order_class = $element['#field_name'] .'-delta-order';
+ $required = !empty($element['#required']) ? '*' : '';
+
+ $header = array(
+ array(
+ 'data' => t('!title: !required', array('!title' => filter_xss_admin($element['#title']), '!required' => $required)),
+ 'colspan' => 2
+ ),
+ t('Order'),
+ );
+ $rows = array();
+
+ foreach (element_children($element) as $key) {
+ if ($key !== $element['#field_name'] .'_add_more') {
+ $element[$key]['_weight']['#attributes']['class'] = $order_class;
+ $delta_element = drupal_render($element[$key]['_weight']);
+ $cells = array(
+ array('data' => '', 'class' => 'content-multiple-drag'),
+ drupal_render($element[$key]),
+ array('data' => $delta_element, 'class' => 'delta-order'),
+ );
+ $rows[] = array(
+ 'data' => $cells,
+ 'class' => 'draggable',
+ );
+ }
+ }
+
+ $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'content-multiple-table'));
+ $output .= $element['#description'] ? '
'. $element['#description'] .'
' : '';
+ $output .= drupal_render($element[$element['#field_name'] .'_add_more']);
+
+ drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
+
+ // Change the button title to reflect the behavior when using JavaScript.
+ // TODO : this should be made a behavior, so it can be reattached when the
+ // form is AHAH-updated
+ $field_name_css = str_replace('_', '-', $field_name);
+ drupal_add_js('if (Drupal.jsEnabled) { $(document).ready(function() { $("#edit-'. $field_name_css .'-'. $field_name_css .'-add-more").val("'. t('Add another item') .'"); }); }', 'inline');
+ }
+ else {
+ foreach (element_children($element) as $key) {
+ $output .= drupal_render($element[$key]);
+ }
+ }
+
+ return $output;
+}
+
+/**
+ * Modules notify Content module when uninstalled, disabled, etc.
+ *
+ * @param string $op
+ * the module operation: uninstall, install, enable, disable
+ * @param string $module
+ * the name of the affected module.
+ * @TODO
+ * figure out exactly what needs to be done by content module when
+ * field modules are installed, uninstalled, enabled or disabled.
+ */
+function content_notify($op, $module) {
+ switch ($op) {
+ case 'install':
+ content_clear_type_cache();
+ break;
+ case 'uninstall':
+ module_load_include('inc', 'content', 'includes/content.crud');
+ content_module_delete($module);
+ content_clear_type_cache(TRUE);
+ break;
+ case 'enable':
+ content_associate_fields($module);
+ content_clear_type_cache();
+ break;
+ case 'disable':
+ content_clear_type_cache(TRUE);
+ break;
+ }
+}
+
+/**
+ * Allows a module to update the database for fields and columns it controls.
+ *
+ * @param string $module
+ * The name of the module to update on.
+ */
+function content_associate_fields($module) {
+ $module_fields = module_invoke($module, 'field_info');
+ if ($module_fields) {
+ foreach ($module_fields as $name => $field_info) {
+ watchdog('content', 'Updating field type %type with module %module.', array('%type' => $name, '%module' => $module));
+ db_query("UPDATE {". content_field_tablename() ."} SET module = '%s', active = %d WHERE type = '%s'", $module, 1, $name);
+ }
+ }
+ $module_widgets = module_invoke($module, 'widget_info');
+ if ($module_widgets) {
+ foreach ($module_widgets as $name => $widget_info) {
+ watchdog('content', 'Updating widget type %type with module %module.', array('%type' => $name, '%module' => $module));
+ db_query("UPDATE {". content_instance_tablename() ."} SET widget_module = '%s', widget_active = %d WHERE widget_type = '%s'", $module, 1, $name);
+ }
+ }
+ // This is called from updates and installs, so get the install-safe
+ // version of a fields array.
+ $fields_set = array();
+ module_load_include('install', 'content');
+ $types = content_types_install();
+ foreach ($types as $type_name => $fields) {
+ foreach ($fields as $field) {
+ if ($field['module'] == $module && !in_array($field['field_name'], $fields_set)) {
+ $columns = module_invoke($field['module'], 'field_settings', 'database columns', $field);
+ db_query("UPDATE {". content_field_tablename() ."} SET db_columns = '%s' WHERE field_name = '%s'", serialize($columns), $field['field_name']);
+ $fields_set[] = $field['field_name'];
+ }
+ }
+ }
+}
+
+/**
+ * Implementation of hook_field(). Handles common field housekeeping.
+ *
+ * This implementation is special, as content.module does not define any field
+ * types. Instead, this function gets called after the type-specific hook, and
+ * takes care of default stuff common to all field types.
+ *
+ * Db-storage ops ('load', 'insert', 'update', 'delete', 'delete revisions')
+ * are not executed field by field, and are thus handled separately in
+ * content_storage.
+ *
+ * The 'view' operation constructs the $node in a way that you can use
+ * drupal_render() to display the formatted output for an individual field.
+ * i.e. print drupal_render($node->field_foo);
+ *
+ * The code now supports both single value formatters, which theme an
+ * individual item value as has been done in previous version of CCK,
+ * and multiple value formatters, which theme all values for the field
+ * in a single theme. The multiple value formatters could be used, for
+ * instance, to plot field values on a single map or display them
+ * in a graph. Single value formatters are the default, multiple value
+ * formatters can be designated as such in formatter_info().
+ *
+ * The node array will look like:
+ * $node->content['field_foo'] = array(
+ * '#type' => 'content_field_view',
+ * '#title' => 'label'
+ * '#field_name' => 'field_name',
+ * '#node' => $node,
+ * 'items' =>
+ * 0 => array(
+ * '#item' => $items[0],
+ * // Only for 'single-value' formatters
+ * '#theme' => $theme,
+ * '#field_name' => 'field_name',
+ * '#type_name' => $node->type,
+ * '#formatter' => $formatter_name,
+ * ),
+ * 1 => array(
+ * '#item' => $items[1],
+ * // Only for 'single-value' formatters
+ * '#theme' => $theme,
+ * '#field_name' => 'field_name',
+ * '#type_name' => $node->type,
+ * '#formatter' => $formatter_name,
+ * ),
+ * // Only for 'multiple-value' formatters
+ * '#theme' => $theme,
+ * '#field_name' => 'field_name',
+ * '#type_name' => $node->type,
+ * '#formatter' => $formatter_name,
+ * ),
+ * );
+ */
+function content_field($op, &$node, $field, &$items, $teaser, $page) {
+ switch ($op) {
+ case 'validate':
+ // TODO : here we could validate that the number of multiple data is correct ?
+ // We're controlling the number of fields to fill out and saving empty
+ // ones if a specified number is requested, so no reason to do any validation
+ // here right now, but if later create a method to indicate whether
+ // 'required' means all values must be filled out, we can come back
+ // here and check that they're not empty.
+ break;
+
+ case 'presave':
+ if ($node->devel_generate) {
+ include_once('./'. drupal_get_path('module', 'content') .'/includes/content.devel.inc');
+ content_generate_fields($node);
+ }
+
+ // Manual node_save calls might not have all fields filled in.
+ // On node insert, we need to make sure all tables get at least an empty
+ // record, or subsequent edits, using drupal_write_record() in update mode,
+ // won't insert any data.
+ // Missing fields on node update are handled in content_storage().
+ if (empty($items) && !isset($node->nid)) {
+ foreach (array_keys($field['columns']) as $column) {
+ $items[0][$column] = NULL;
+ }
+ $node->$field['field_name'] = $items;
+ }
+
+ // If there was an AHAH add more button in this field, don't save it.
+ // TODO : is it still needed ?
+ unset($items[$field['field_name'] .'_add_more']);
+
+ // If content module is handling multiple values, don't save empty items.
+ if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
+ // Filter out empty values.
+ $items = content_set_empty($field, $items);
+ // Reorder items to account for drag-n-drop reordering.
+ $items = _content_sort_items($field, $items);
+ }
+ break;
+
+ case 'view':
+ $element = array();
+
+ if ($node->build_mode == NODE_BUILD_NORMAL) {
+ $context = $teaser ? 'teaser' : 'full';
+ }
+ else {
+ $context = $node->build_mode;
+ }
+ // Do not include field labels when indexing content.
+ if ($context == NODE_BUILD_SEARCH_INDEX) {
+ $field['display_settings']['label']['format'] = 'hidden';
+ }
+
+ $field_types = _content_field_types();
+ $formatters = $field_types[$field['type']]['formatters'];
+ $formatter_name = isset($field['display_settings'][$context]['format']) ? $field['display_settings'][$context]['format'] : 'default';
+
+ if (!isset($formatters[$formatter_name]) && $formatter_name != 'hidden') {
+ // This might happen when the selected formatter has been renamed in the
+ // module, or if the module has been disabled since then.
+ $formatter_name = 'default';
+ }
+
+ if (isset($formatters[$formatter_name])) {
+ $formatter = $formatters[$formatter_name];
+ $theme = $formatter['module'] .'_formatter_'. $formatter_name;
+ $single = (content_handle('formatter', 'multiple values', $formatter) == CONTENT_HANDLE_CORE);
+
+ $element = array(
+ '#type' => 'content_field_view',
+ '#title' => $field['widget']['label'],
+ '#weight' => $field['widget']['weight'],
+ '#field_name' => $field['field_name'],
+ '#access' => $formatter_name != 'hidden',
+ '#node' => $node,
+ '#teaser' => $teaser,
+ '#page' => $page,
+ '#single' => $single,
+ 'items' => array(),
+ );
+
+ // Fill-in items.
+ foreach ($items as $delta => $item) {
+ $element['items'][$delta] = array(
+ '#item' => $item,
+ '#weight' => $delta,
+ );
+ }
+
+ // Append formatter information either on each item ('single-value' formatter)
+ // or at the upper 'items' level ('multiple-value' formatter)
+ $format_info = array(
+ '#theme' => $theme,
+ '#field_name' => $field['field_name'],
+ '#type_name' => $node->type,
+ '#formatter' => $formatter_name,
+ );
+ if ($single) {
+ foreach ($items as $delta => $item) {
+ $element['items'][$delta] += $format_info;
+ }
+ }
+ else {
+ $element['items'] += $format_info;
+ }
+
+ }
+
+ return array($field['field_name'] => $element);
+
+ case 'alter':
+ // Add back the formatted values in the 'view' element,
+ // so that node templates can use it.
+ if (isset($node->content[$field['field_name']])) {
+ $element = $node->content[$field['field_name']];
+ if ($element['#single']) {
+ // Single value formatter.
+ foreach (element_children($element['items']) as $delta) {
+ // Use isset() to avoid undefined index message on #children when field values are empty.
+ $items[$delta]['view'] = isset($element['items'][$delta]['#children']) ? $element['items'][$delta]['#children'] : '';
+ }
+ }
+ else {
+ // Multiple values formatter.
+ $items[0]['view'] = $element['items']['#children'];
+ }
+ }
+ else {
+ $items[0]['view'] = '';
+ }
+ break;
+
+ case 'prepare translation':
+ $addition = array();
+ if (isset($node->translation_source->$field['field_name'])) {
+ $addition[$field['field_name']] = $node->translation_source->$field['field_name'];
+ }
+ return $addition;
+ }
+}
+
+/**
+ * Helper function to set NULL values and unset items where needed.
+ *
+ * If a specified number of multiple values was requested or this is
+ * the zero delta, save even if empty and keep a NULL value for each
+ * of its columns so there is a marker row in the database, otherwise
+ * unset the item.
+ *
+ * @param array $field
+ * @param array $items
+ * @return array
+ * returns emptied and adjusted item array
+ */
+function content_set_empty($field, $items) {
+ $function = $field['module'] .'_content_is_empty';
+ $max_delta = $field['multiple'] > 1 ? $field['multiple'] : 0;
+ foreach ((array) $items as $delta => $item) {
+ if ($function($item, $field)) {
+ if ($delta <= $max_delta) {
+ foreach (array_keys($field['columns']) as $column) {
+ $items[$delta][$column] = NULL;
+ }
+ }
+ else {
+ unset($items[$delta]);
+ }
+ }
+ }
+ return $items;
+}
+
+/**
+ * Helper function to sort items in a field according to
+ * user drag-n-drop reordering.
+ */
+function _content_sort_items($field, $items) {
+ if ($field['multiple'] >= 1 && isset($items[0]['_weight'])) {
+ usort($items, '_content_sort_items_helper');
+ foreach ($items as $delta => $item) {
+ unset($items[$delta]['_weight']);
+ }
+ }
+ return $items;
+}
+
+/**
+ * Sort function for items order.
+ * (copied form element_sort(), which acts on #weight keys)
+ */
+function _content_sort_items_helper($a, $b) {
+ $a_weight = (is_array($a) && isset($a['_weight'])) ? $a['_weight'] : 0;
+ $b_weight = (is_array($b) && isset($b['_weight'])) ? $b['_weight'] : 0;
+ if ($a_weight == $b_weight) {
+ return 0;
+ }
+ return ($a_weight < $b_weight) ? -1 : 1;
+}
+
+/**
+ * TODO : PHPdoc
+ */
+function content_storage($op, $node) {
+ $type_name = $node->type;
+ $type = content_types($type_name);
+
+ switch ($op) {
+ case 'load':
+ // OPTIMIZE : load all non multiple fields in a single JOIN query ?
+ // warning : 61-join limit in MySQL ?
+ $additions = array();
+ // For each table used by this content type,
+ foreach ($type['tables'] as $table) {
+ $schema = drupal_get_schema($table);
+ $query = 'SELECT * FROM {'. $table .'} WHERE vid = %d';
+
+ // If we're loading a table for a multiple field,
+ // we fetch all rows (values) ordered by delta,
+ // else we only fetch one row.
+ $result = isset($schema['fields']['delta']) ? db_query($query .' ORDER BY delta', $node->vid) : db_query_range($query, $node->vid, 0, 1);
+
+ // For each table row, populate the fields.
+ while ($row = db_fetch_array($result)) {
+ // For each field stored in the table, add the field item.
+ foreach ($schema['content fields'] as $field_name) {
+ $item = array();
+ $field = content_fields($field_name, $type_name);
+ $db_info = content_database_info($field);
+ // For each column declared by the field, populate the item.
+ foreach ($db_info['columns'] as $column => $attributes) {
+ $item[$column] = $row[$attributes['column']];
+ }
+
+ // Add the item to the field values for the node.
+ if (!isset($additions[$field_name])) {
+ $additions[$field_name] = array();
+ }
+ $additions[$field_name][] = $item;
+ }
+ }
+ }
+ return $additions;
+
+ case 'insert':
+ case 'update':
+ foreach ($type['tables'] as $table) {
+ $schema = drupal_get_schema($table);
+ $record = array();
+ foreach ($schema['content fields'] as $field_name) {
+ if (isset($node->$field_name)) {
+ $field = content_fields($field_name, $type_name);
+ // Multiple fields need specific handling, we'll deal with them later on.
+ if ($field['multiple']) {
+ continue;
+ }
+ $db_info = content_database_info($field);
+ foreach ($db_info['columns'] as $column => $attributes) {
+ $record[$attributes['column']] = $node->{$field_name}[0][$column];
+ }
+ }
+ }
+ if (count($record)) {
+ $record['nid'] = $node->nid;
+ $record['vid'] = $node->vid;
+ // Can't rely on the insert/update op of the node to decide if this
+ // is an insert or an update, a node or revision may have existed
+ // before any fields were created, so there may not be an entry here.
+
+ // TODO - should we auto create an entry for all existing nodes when
+ // fields are added to content types -- either a NULL value
+ // or the default value? May need to offer the user an option of
+ // how to handle that.
+ if (db_result(db_query("SELECT COUNT(*) FROM {". $table ."} WHERE vid = %d", $node->vid))) {
+ content_write_record($table, $record, array('vid'));
+ }
+ else {
+ content_write_record($table, $record);
+ }
+ }
+ }
+
+ // Handle multiple fields.
+ foreach ($type['fields'] as $field) {
+ if ($field['multiple'] && isset($node->$field['field_name'])) {
+ $db_info = content_database_info($field);
+ // Delete and insert, rather than update, in case a value was added.
+ if ($op == 'update') {
+ db_query('DELETE FROM {'. $db_info['table'] .'} WHERE vid = %d', $node->vid);
+ }
+ foreach ($node->$field['field_name'] as $delta => $item) {
+ $record = array();
+ foreach ($db_info['columns'] as $column => $attributes) {
+ $record[$attributes['column']] = $item[$column];
+ }
+ $record['nid'] = $node->nid;
+ $record['vid'] = $node->vid;
+ $record['delta'] = $delta;
+ content_write_record($db_info['table'], $record);
+ }
+ }
+ }
+ break;
+
+ case 'delete':
+ foreach ($type['tables'] as $table) {
+ db_query('DELETE FROM {'. $table .'} WHERE nid = %d', $node->nid);
+ }
+ break;
+
+ case 'delete revision':
+ foreach ($type['tables'] as $table) {
+ db_query('DELETE FROM {'. $table .'} WHERE vid = %d', $node->vid);
+ }
+ break;
+ }
+}
+
+/**
+ * Save a record to the database based upon the schema.
+ *
+ * Directly copied from core's drupal_write_record, which can't update a
+ * column to NULL.
+ *
+ * Default values are filled in for missing items, and 'serial' (auto increment)
+ * types are filled in with IDs.
+ *
+ * @param $table
+ * The name of the table; this must exist in schema API.
+ * @param $object
+ * The object to write. This is a reference, as defaults according to
+ * the schema may be filled in on the object, as well as ID on the serial
+ * type(s). Both array an object types may be passed.
+ * @param $update
+ * If this is an update, specify the primary keys' field names. It is the
+ * caller's responsibility to know if a record for this object already
+ * exists in the database. If there is only 1 key, you may pass a simple string.
+ * @return
+ * Failure to write a record will return FALSE. Otherwise SAVED_NEW or
+ * SAVED_UPDATED is returned depending on the operation performed. The
+ * $object parameter contains values for any serial fields defined by
+ * the $table. For example, $object->nid will be populated after inserting
+ * a new node.
+ */
+function content_write_record($table, &$object, $update = array()) {
+ // Standardize $update to an array.
+ if (is_string($update)) {
+ $update = array($update);
+ }
+
+ // Convert to an object if needed.
+ if (is_array($object)) {
+ $object = (object) $object;
+ $array = TRUE;
+ }
+ else {
+ $array = FALSE;
+ }
+
+ $schema = drupal_get_schema($table);
+ if (empty($schema)) {
+ return FALSE;
+ }
+
+ $fields = $defs = $values = $serials = $placeholders = array();
+
+ // Go through our schema, build SQL, and when inserting, fill in defaults for
+ // fields that are not set.
+ foreach ($schema['fields'] as $field => $info) {
+ // Special case -- skip serial types if we are updating.
+ if ($info['type'] == 'serial' && count($update)) {
+ continue;
+ }
+
+ // For inserts, populate defaults from Schema if not already provided
+ if (!isset($object->$field) && !count($update) && isset($info['default'])) {
+ $object->$field = $info['default'];
+ }
+
+ // Track serial fields so we can helpfully populate them after the query.
+ if ($info['type'] == 'serial') {
+ $serials[] = $field;
+ // Ignore values for serials when inserting data. Unsupported.
+ unset($object->$field);
+ }
+
+ // Build arrays for the fields, placeholders, and values in our query.
+ if (isset($object->$field) || array_key_exists($field, $object)) {
+ $fields[] = $field;
+ if (isset($object->$field)) {
+ $placeholders[] = db_type_placeholder($info['type']);
+
+ if (empty($info['serialize'])) {
+ $values[] = $object->$field;
+ }
+ else {
+ $values[] = serialize($object->$field);
+ }
+ }
+ else {
+ $placeholders[] = 'NULL';
+ }
+ }
+ }
+
+ // Build the SQL.
+ $query = '';
+ if (!count($update)) {
+ $query = "INSERT INTO {". $table ."} (". implode(', ', $fields) .') VALUES ('. implode(', ', $placeholders) .')';
+ $return = SAVED_NEW;
+ }
+ else {
+ $query = '';
+ foreach ($fields as $id => $field) {
+ if ($query) {
+ $query .= ', ';
+ }
+ $query .= $field .' = '. $placeholders[$id];
+ }
+
+ foreach ($update as $key){
+ $conditions[] = "$key = ". db_type_placeholder($schema['fields'][$key]['type']);
+ $values[] = $object->$key;
+ }
+
+ $query = "UPDATE {". $table ."} SET $query WHERE ". implode(' AND ', $conditions);
+ $return = SAVED_UPDATED;
+ }
+
+ // Execute the SQL.
+ if (db_query($query, $values)) {
+ if ($serials) {
+ // Get last insert ids and fill them in.
+ foreach ($serials as $field) {
+ $object->$field = db_last_insert_id($table, $field);
+ }
+ }
+
+ // If we began with an array, convert back so we don't surprise the caller.
+ if ($array) {
+ $object = (array) $object;
+ }
+
+ return $return;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Invoke a field hook.
+ *
+ * For each operation, both this function and _content_field_invoke_default() are
+ * called so that the default database handling can occur.
+ */
+function _content_field_invoke($op, &$node, $teaser = NULL, $page = NULL) {
+ $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
+ $type = content_types($type_name);
+ $field_types = _content_field_types();
+
+ $return = array();
+ foreach ($type['fields'] as $field) {
+ $items = isset($node->$field['field_name']) ? $node->$field['field_name'] : array();
+
+ // Make sure AHAH 'add more' button isn't sent to the fields for processing.
+ unset($items[$field['field_name'] .'_add_more']);
+
+ $module = $field_types[$field['type']]['module'];
+ $function = $module .'_field';
+ if (function_exists($function)) {
+ $result = $function($op, $node, $field, $items, $teaser, $page);
+ if (is_array($result)) {
+ $return = array_merge($return, $result);
+ }
+ else if (isset($result)) {
+ $return[] = $result;
+ }
+ }
+ // test for values in $items in case modules added items on insert
+ if (isset($node->$field['field_name']) || count($items)) {
+ $node->$field['field_name'] = $items;
+ }
+ }
+ return $return;
+}
+
+/**
+ * Invoke content.module's version of a field hook.
+ */
+function _content_field_invoke_default($op, &$node, $teaser = NULL, $page = NULL) {
+ $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
+ $type = content_types($type_name);
+ $field_types = _content_field_types();
+
+ $return = array();
+ // The operations involving database queries are better off handled by table
+ // rather than by field.
+ if (in_array($op, array('load', 'insert', 'update', 'delete', 'delete revision'))) {
+ return content_storage($op, $node);
+ }
+ else {
+ foreach ($type['fields'] as $field) {
+ $items = isset($node->$field['field_name']) ? $node->$field['field_name'] : array();
+ $result = content_field($op, $node, $field, $items, $teaser, $page);
+ if (is_array($result)) {
+ $return = array_merge($return, $result);
+ }
+ else if (isset($result)) {
+ $return[] = $result;
+ }
+ if (isset($node->$field['field_name'])) {
+ $node->$field['field_name'] = $items;
+ }
+ }
+ }
+ return $return;
+}
+
+/**
+ * Return a list of all content types.
+ *
+ * @param $content_type_name
+ * If set, return information on just this type.
+ */
+function content_types($type_name = NULL) {
+ // handle type name with either an underscore or a dash
+ $type_name = !empty($type_name) ? str_replace('-', '_', $type_name) : NULL;
+
+ $info = _content_type_info();
+ if (isset($info['content types'])) {
+ if (!isset($type_name)) {
+ return $info['content types'];
+ }
+ if (isset($info['content types'][$type_name])) {
+ return $info['content types'][$type_name];
+ }
+ }
+}
+
+/**
+ * Return a list of all fields.
+ *
+ * @param $field_name
+ * If set, return information on just this field.
+ * @param $content_type_name
+ * If set, return information of the field within the context of this content
+ * type.
+ */
+function content_fields($field_name = NULL, $content_type_name = NULL) {
+ $info = _content_type_info();
+ if (isset($info['fields'])) {
+ if (!isset($field_name)) {
+ return $info['fields'];
+ }
+ if (isset($info['fields'][$field_name])) {
+ if (!isset($content_type_name)) {
+ return $info['fields'][$field_name];
+ }
+ if (isset($info['content types'][$content_type_name]['fields'][$field_name])) {
+ return $info['content types'][$content_type_name]['fields'][$field_name];
+ }
+ }
+ }
+}
+
+/**
+ * Return a list of field types.
+ */
+function _content_field_types() {
+ $info = _content_type_info();
+ return isset($info['field types']) ? $info['field types'] : array();
+}
+
+/**
+ * Return a list of widget types.
+ */
+function _content_widget_types() {
+ $info = _content_type_info();
+ return isset($info['widget types']) ? $info['widget types'] : array();
+}
+
+/**
+ * Collate all information on content types, fields, and related structures.
+ *
+ * @param $reset
+ * If TRUE, clear the cache and fetch the information from the database again.
+ */
+function _content_type_info($reset = FALSE) {
+ static $info;
+
+ if ($reset || !isset($info)) {
+ // Make sure this function doesn't run until the tables have been created,
+ // for instance, when first enabled and called from content_menu().
+ if (!db_table_exists(content_field_tablename()) || !db_table_exists(content_instance_tablename())) {
+ return array();
+ }
+
+ if ($cached = cache_get('content_type_info', content_cache_tablename())) {
+ $info = $cached->data;
+ }
+ else {
+ $info = array(
+ 'field types' => array(),
+ 'widget types' => array(),
+ 'fields' => array(),
+ 'content types' => array(),
+ );
+
+ // Populate field types.
+ foreach (module_list() as $module) {
+ $module_field_types = module_invoke($module, 'field_info');
+ if ($module_field_types) {
+ foreach ($module_field_types as $name => $field_info) {
+ // Truncate names to match the value that is stored in the database.
+ $db_name = substr($name, 0, 32);
+ $info['field types'][$db_name] = $field_info;
+ $info['field types'][$db_name]['module'] = $module;
+ $info['field types'][$db_name]['formatters'] = array();
+ }
+ }
+ }
+
+ // Populate widget types and formatters for known field types.
+ foreach (module_list() as $module) {
+ if ($module_widgets = module_invoke($module, 'widget_info')) {
+ foreach ($module_widgets as $name => $widget_info) {
+ // Truncate names to match the value that is stored in the database.
+ $db_name = substr($name, 0, 32);
+ $info['widget types'][$db_name] = $widget_info;
+ $info['widget types'][$db_name]['module'] = $module;
+ // Replace field types with db_compatible version of known field types.
+ $info['widget types'][$db_name]['field types'] = array();
+ foreach ($widget_info['field types'] as $field_type) {
+ $field_type_db_name = substr($field_type, 0, 32);
+ if (isset($info['field types'][$field_type_db_name])) {
+ $info['widget types'][$db_name]['field types'][] = $field_type_db_name;
+ }
+ }
+ }
+ }
+
+ if ($module_formatters = module_invoke($module, 'field_formatter_info')) {
+ foreach ($module_formatters as $name => $formatter_info) {
+ foreach ($formatter_info['field types'] as $field_type) {
+ // Truncate names to match the value that is stored in the database.
+ $db_name = substr($field_type, 0, 32);
+ if (isset($info['field types'][$db_name])) {
+ $info['field types'][$db_name]['formatters'][$name] = $formatter_info;
+ $info['field types'][$db_name]['formatters'][$name]['module'] = $module;
+ }
+ }
+ }
+ }
+ }
+
+ // Populate actual field instances.
+ module_load_include('inc', 'content', 'includes/content.crud');
+ foreach (node_get_types() as $type_name => $data) {
+ $type = (array) $data;
+ $type['url_str'] = str_replace('_', '-', $type['type']);
+ $type['fields'] = array();
+ $type['tables'] = array();
+ $fields = content_field_instance_read(array('type_name' => $type_name));
+ foreach ($fields as $field) {
+ $type['fields'][$field['field_name']] = $field;
+ $db_info = content_database_info($field);
+ $type['tables'][$db_info['table']] = $db_info['table'];
+ $info['fields'][$field['field_name']] = $field;
+ }
+
+ // Gather information about non-CCK 'fields'.
+ $extra = module_invoke_all('content_extra_fields', $type_name);
+ drupal_alter('content_extra_fields', $extra, $type_name);
+ // Add saved weights.
+ foreach (variable_get('content_extra_weights_'. $type_name, array()) as $key => $value) {
+ // Some stored entries might not exist anymore, for instance if uploads
+ // have been disabled, or vocabularies removed...
+ if (isset($extra[$key])) {
+ $extra[$key]['weight'] = $value;
+ }
+ }
+ $type['extra'] = $extra;
+
+ $info['content types'][$type_name] = $type;
+ }
+
+ cache_set('content_type_info', $info, content_cache_tablename());
+ }
+ }
+ return $info;
+}
+
+/**
+ * Implementation of hook_node_type()
+ * React to change in node types
+ */
+function content_node_type($op, $info) {
+ switch ($op) {
+ case 'insert':
+ module_load_include('inc', 'content', 'includes/content.crud');
+ content_type_create($info);
+ break;
+ case 'update':
+ module_load_include('inc', 'content', 'includes/content.crud');
+ content_type_update($info);
+ break;
+ case 'delete':
+ module_load_include('inc', 'content', 'includes/content.crud');
+ content_type_delete($info);
+ break;
+ }
+}
+
+/**
+ * Clear the cache of content_types; called in several places when content
+ * information is changed.
+ */
+function content_clear_type_cache($rebuild_schema = FALSE) {
+ cache_clear_all('*', content_cache_tablename(), TRUE);
+ _content_type_info(TRUE);
+
+ // Refresh the schema to pick up new information.
+ if ($rebuild_schema) {
+ $schema = drupal_get_schema(NULL, TRUE);
+ }
+
+ if (module_exists('views')) {
+ // Needed because this can be called from .install files
+ module_load_include('module', 'views');
+ views_invalidate_cache();
+ }
+}
+
+/**
+ * Retrieve the database storage location(s) for a field.
+ *
+ * TODO : add a word about why it's not included in the global _content_type_info array.
+ *
+ * @param $field
+ * The field whose database information is requested.
+ * @return
+ * An array with the keys:
+ * "table": The name of the database table where the field data is stored.
+ * "columns": An array of columns stored for this field. Each is a collection
+ * of information returned from hook_field_settings('database columns'),
+ * with the addition of a "column" attribute which holds the name of the
+ * database column that stores the data.
+ */
+function content_database_info($field) {
+ $db_info = array();
+
+ if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) {
+ $db_info['table'] = _content_tablename($field['field_name'], CONTENT_DB_STORAGE_PER_FIELD);
+ }
+ else {
+ $db_info['table'] = _content_tablename($field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
+ }
+
+ $db_info['columns'] = (array) $field['columns'];
+ // Generate column names for this field from generic column names.
+ foreach ($db_info['columns'] as $column_name => $attributes) {
+ $db_info['columns'][$column_name]['column'] = $field['field_name'] .'_'. $column_name;
+ }
+
+ return $db_info;
+}
+
+/**
+ * Helper function for identifying the storage type for a field.
+ *
+ * TODO: This should depend on 'shareable', not # of instances.
+ */
+function content_storage_type($field) {
+ if ($field['multiple'] > 0 || $field['shareable']) {
+ return CONTENT_DB_STORAGE_PER_FIELD;
+ }
+ return CONTENT_DB_STORAGE_PER_CONTENT_TYPE;
+}
+
+/**
+ * Manipulate a 2D array to reverse rows and columns.
+ *
+ * The default data storage for fields is delta first, column names second.
+ * This is sometimes inconvenient for field modules, so this function can be
+ * used to present the data in an alternate format.
+ *
+ * @param $array
+ * The array to be transposed. It must be at least two-dimensional, and
+ * the subarrays must all have the same keys or behavior is undefined.
+ * @return
+ * The transposed array.
+ */
+function content_transpose_array_rows_cols($array) {
+ $result = array();
+ if (is_array($array)) {
+ foreach ($array as $key1 => $value1) {
+ if (is_array($value1)) {
+ foreach ($value1 as $key2 => $value2) {
+ if (!isset($result[$key2])) {
+ $result[$key2] = array();
+ }
+ $result[$key2][$key1] = $value2;
+ }
+ }
+ }
+ }
+ return $result;
+}
+
+/**
+ * Create an array of the allowed values for this field.
+ *
+ * Used by number and text fields, expects to find either
+ * PHP code that will return the correct value, or a string
+ * with keys and labels separated with '|' and with each
+ * new value on its own line.
+ */
+function content_allowed_values($field) {
+ static $allowed_values;
+
+ if (isset($allowed_values[$field['field_name']])) {
+ return $allowed_values[$field['field_name']];
+ }
+
+ $allowed_values[$field['field_name']] = array();
+
+ if (isset($field['allowed_values_php'])) {
+ ob_start();
+ $result = eval($field['allowed_values_php']);
+ if (is_array($result)) {
+ $allowed_values[$field['field_name']] = $result;
+ }
+ ob_end_clean();
+ }
+
+ if (empty($allowed_values[$field['field_name']]) && isset($field['allowed_values'])) {
+ $list = explode("\n", $field['allowed_values']);
+ $list = array_map('trim', $list);
+ $list = array_filter($list, 'strlen');
+ foreach ($list as $opt) {
+ if (strpos($opt, '|') !== FALSE) {
+ list($key, $value) = explode('|', $opt);
+ $allowed_values[$field['field_name']][$key] = $value ? $value : $key;
+ }
+ else {
+ $allowed_values[$field['field_name']][$opt] = $opt;
+ }
+ }
+ }
+ return $allowed_values[$field['field_name']];
+}
+
+/**
+ * Format a field item for display.
+ *
+ * Used to display a field's values outside the context of the $node, as
+ * when fields are displayed in Views, or to display a field in a template
+ * using a different formatter than the one set up on the Display Fields tab
+ * for the node's context.
+ *
+ * @param $field
+ * Either a field array or the name of the field.
+ * @param $item
+ * The field item(s) to be formatted (such as $node->field_foo[0],
+ * or $node->field_foo if the formatter handles multiple values itself)
+ * @param $formatter
+ * The name of the formatter to use.
+ * @param $node
+ * Optionally, the containing node object for context purposes and
+ * field-instance options.
+ *
+ * @return
+ * A string containing the contents of the field item(s) sanitized for display.
+ * It will have been passed through the necessary check_plain() or check_markup()
+ * functions as necessary.
+ */
+function content_format($field, $item, $formatter = 'default', $node = NULL) {
+ if (!is_array($field)) {
+ $field = content_fields($field);
+ }
+ $field_types = _content_field_types();
+ $formatters = $field_types[$field['type']]['formatters'];
+ if (!isset($formatters[$formatter])) {
+ $formatter = 'default';
+ }
+
+ $formatter_name = $formatter;
+ $formatter = $formatters[$formatter_name];
+ $theme = $formatter['module'] .'_formatter_'. $formatter_name;
+
+ $element = array(
+ '#theme' => $theme,
+ '#field_name' => $field['field_name'],
+ '#type_name' => isset($node->type) ? $node->type : '',
+ '#formatter' => $formatter_name,
+ );
+
+ if (content_handle('formatter', 'multiple values', $formatter) == CONTENT_HANDLE_CORE) {
+ // Single value formatter.
+
+ // hook_field('sanitize') expects an array of items, so we build one.
+ $items = array($item);
+ $function = $field['module'] .'_field';
+ if (function_exists($function)) {
+ $function('sanitize', $node, $field, $items, FALSE, FALSE);
+ }
+
+ $element['#item'] = $items[0];
+ }
+ else {
+ // Multiple values formatter.
+ $items = $item;
+ $function = $field['module'] .'_field';
+ if (function_exists($function)) {
+ $function('sanitize', $node, $field, $items, FALSE, FALSE);
+ }
+
+ foreach ($items as $delta => $item) {
+ $element[$delta] = array(
+ '#item' => $item,
+ '#weight' => $delta,
+ );
+ }
+ }
+
+ return theme($theme, $element);
+}
+
+/**
+ * Array of possible display contexts for fields.
+ */
+function _content_admin_display_contexts($selector = CONTENT_CONTEXTS_ALL) {
+ $contexts = array();
+
+ if ($selector == CONTENT_CONTEXTS_ALL || $selector == CONTENT_CONTEXTS_SIMPLE) {
+ $contexts['teaser'] = t('Teaser');
+ $contexts['full'] = t('Full node');
+ }
+
+ if ($selector == CONTENT_CONTEXTS_ALL || $selector == CONTENT_CONTEXTS_ADVANCED) {
+ $contexts[NODE_BUILD_RSS] = t('RSS Item');
+ if (module_exists('search')) {
+ $contexts[NODE_BUILD_SEARCH_INDEX] = t('Search Index');
+ $contexts[NODE_BUILD_SEARCH_RESULT] = t('Search Result');
+ }
+ }
+
+ return $contexts;
+}
+
+/**
+ * Generate a table name for a field or a content type.
+ *
+ * @param $name
+ * The name of the content type or content field
+ * @param $storage
+ * CONTENT_DB_STORAGE_PER_FIELD or CONTENT_DB_STORAGE_PER_CONTENT_TYPE
+ * @return
+ * A string containing the generated name for the database table
+ */
+function _content_tablename($name, $storage, $version = NULL) {
+ if (is_null($version)) {
+ $version = variable_get('content_schema_version', 0);
+ }
+
+ if ($version < 1003) {
+ $version = 0;
+ }
+ else {
+ $version = 1003;
+ }
+
+ $name = str_replace('-', '_', $name);
+ switch ("$version-$storage") {
+ case '0-'. CONTENT_DB_STORAGE_PER_CONTENT_TYPE :
+ return "node_$name";
+ case '0-'. CONTENT_DB_STORAGE_PER_FIELD :
+ return "node_data_$name";
+ case '1003-'. CONTENT_DB_STORAGE_PER_CONTENT_TYPE :
+ return "content_type_$name";
+ case '1003-'. CONTENT_DB_STORAGE_PER_FIELD :
+ return "content_$name";
+ }
+}
+
+/**
+ * Generate table name for the content field table.
+ *
+ * Needed because the table name changes depending on version.
+ * Using 'content_node_field' instead of 'content_field'
+ * to avoid conflicts with field tables that will be prefixed
+ * with 'content_field'.
+ */
+function content_field_tablename($version = NULL) {
+ if (is_null($version)) {
+ $version = variable_get('content_schema_version', 0);
+ }
+ return $version < 6001 ? 'node_field' : 'content_node_field';
+}
+
+/**
+ * Generate table name for the content field instance table.
+ *
+ * Needed because the table name changes depending on version.
+ */
+function content_instance_tablename($version = NULL) {
+ if (is_null($version)) {
+ $version = variable_get('content_schema_version', 0);
+ }
+ return $version < 6001 ? 'node_field_instance' : 'content_node_field_instance';
+}
+
+/**
+ * Generate table name for the content cache table.
+ *
+ * Needed because the table name changes depending on version. Because of
+ * a new database column, the content_cache table will be unusable until
+ * update 6000 runs, so the cache table will be used instead.
+ */
+function content_cache_tablename() {
+ if (variable_get('content_schema_version', -1) < 6000) {
+ return 'cache';
+ }
+ else {
+ return 'cache_content';
+ }
+}
+
+/**
+ * A basic schema used by all field and type tables.
+ *
+ * This will only add the columns relevant for the specified field.
+ * Leave $field['columns'] empty to get only the base schema,
+ * otherwise the function will return the whole thing.
+ */
+function content_table_schema($field = NULL) {
+ $schema = array(
+ 'fields' => array(
+ 'vid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+ 'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0)
+ ),
+ 'primary key' => array('vid'),
+ );
+
+ // Add delta column if needed.
+ if (!empty($field['multiple'])) {
+ $schema['fields']['delta'] = array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0);
+ $schema['primary key'][] = 'delta';
+ }
+ $schema['content fields'] = array();
+
+ // Add field columns column if needed.
+ // This function is called from install files where it is not safe
+ // to use content_fields() or content_database_info(), so we
+ // just used the column values stored in the $field.
+ // We also need the schema to include fields from disabled modules
+ // or there will be no way to delete those fields.
+
+ if (!empty($field['columns'])) {
+ foreach ($field['columns'] as $column => $attributes) {
+ $column_name = $field['field_name'] .'_'. $column;
+ unset($attributes['column']);
+ unset($attributes['sortable']);
+ $schema['fields'][$column_name] = $attributes;
+ }
+ $schema['content fields'][] = $field['field_name'];
+ }
+ return $schema;
+}
+
+/**
+ * Helper function for determining the behavior of a field or a widget
+ * with respect to a given operation. (currently used for field 'view',
+ * and widget 'default values' and 'multiple values')
+ *
+ * @param $entity
+ * 'field' or 'widget'
+ * @param $op
+ * the name of the operation ('view', 'default value'...)
+ * @param $field
+ * The field array, including widget info.
+ * @return
+ * CONTENT_CALLBACK_NONE - do nothing for this operation
+ * CONTENT_CALLBACK_CUSTOM - use the module's callback function.
+ * CONTENT_CALLBACK_DEFAULT - use content module default behavior
+ *
+ */
+function content_callback($entity, $op, $field) {
+ switch ($entity) {
+ case 'field':
+ $info = module_invoke($field['module'], "field_info");
+ return isset($info[$field['type']]['callbacks'][$op]) ? $info[$field['type']]['callbacks'][$op] : CONTENT_CALLBACK_DEFAULT;
+
+ case 'widget':
+ $info = module_invoke($field['widget']['module'], "widget_info");
+ return isset($info[$field['widget']['type']]['callbacks'][$op]) ? $info[$field['widget']['type']]['callbacks'][$op] : CONTENT_CALLBACK_DEFAULT;
+ }
+}
+
+/**
+ * Helper function for determining the handling of a field, widget or
+ * formatter with respect to a given operation.
+ *
+ * Currently used for widgets and formatters 'multiple values'.
+ *
+ * @param $entity
+ * 'field', 'widget' or 'formatter'
+ * @param $op
+ * the name of the operation ('default values'...)
+ * @param $object
+ * - if $entity is 'field' or 'widget' : the field array,
+ * including widget info.
+ * - if $entity is 'formater' : the formatter array.
+ * @return
+ * CONTENT_HANDLE_CORE - the content module handles this operation.
+ * CONTENT_HANDLE_MODULE - the implementing module handles this operation.
+ */
+function content_handle($entity, $op, $object) {
+ switch ($entity) {
+ case 'field':
+ $info = module_invoke($object['module'], "field_info");
+ return isset($info[$object['type']][$op]) ? $info[$object['type']][$op] : CONTENT_HANDLE_CORE;
+
+ case 'widget':
+ $info = module_invoke($object['widget']['module'], "widget_info");
+ return isset($info[$object['widget']['type']][$op]) ? $info[$object['widget']['type']][$op] : CONTENT_HANDLE_CORE;
+
+ case 'formatter':
+ // Much simpler, formatters arrays *are* the 'formatter_info' itself.
+ // We let content_handle deal with them only for code consistency.
+ return isset($object[$op]) ? $object[$op] : CONTENT_HANDLE_CORE;
+ }
+}
+
+/**
+ * Helper function to return the correct default value for a field.
+ *
+ * @param $node
+ * The node.
+ * @param $field
+ * The field array.
+ * @param $items
+ * The value of the field in the node.
+ * @return
+ * The default value for that field.
+ */
+function content_default_value(&$form, &$form_state, $field, $delta) {
+ $widget_types = _content_widget_types();
+ $module = $widget_types[$field['widget']['type']]['module'];
+
+ $default_value = array();
+ if (!empty($field['widget']['default_value_php'])) {
+ ob_start();
+ $result = eval($field['widget']['default_value_php']);
+ ob_end_clean();
+ if (is_array($result)) {
+ $default_value = $result;
+ }
+ }
+ elseif (!empty($field['widget']['default_value'])) {
+ $default_value = $field['widget']['default_value'];
+ }
+ return (array) $default_value;
+}
+
+/**
+ * Hook elements().
+ *
+ * Used to add multiple value processing, validation, and themes.
+ *
+ * FAPI callbacks can be declared here, and the element will be
+ * passed to those callbacks.
+ *
+ * Drupal will automatically theme the element using a theme with
+ * the same name as the hook_elements key.
+ */
+function content_elements() {
+ return array(
+ 'content_multiple_values' => array(),
+ 'content_field_view' => array(),
+ );
+}
+
+/**
+ * Process variables for field.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $node
+ * - $field
+ * - $items
+ * - $teaser
+ * - $page
+ *
+ * @see field.tpl.php
+ */
+function template_preprocess_content_field_view(&$variables) {
+ $element = $variables['element'];
+ $field = content_fields($element['#field_name'], $element['#node']->type);
+
+ $variables['node'] = $element['#node'];
+ $variables['field'] = $field;
+ $variables['items'] = array();
+
+ if ($element['#single']) {
+ // Single value formatter.
+ foreach (element_children($element['items']) as $delta) {
+ $variables['items'][$delta] = $element['items'][$delta]['#item'];
+ // Use isset() to avoid undefined index message on #children when field values are empty.
+ $variables['items'][$delta]['view'] = isset($element['items'][$delta]['#children']) ? $element['items'][$delta]['#children'] : '';
+ }
+ }
+ else {
+ // Multiple values formatter.
+ // We display the 'all items' output as $items[0], as if it was the
+ // output of a single valued field.
+ // Raw values are still exposed for all items.
+ foreach (element_children($element['items']) as $delta) {
+ $variables['items'][$delta] = $element['items'][$delta]['#item'];
+ }
+ $variables['items'][0]['view'] = $element['items']['#children'];
+ }
+
+ $variables['teaser'] = $element['#teaser'];
+ $variables['page'] = $element['#page'];
+
+ $field_empty = TRUE;
+
+ foreach ($variables['items'] as $delta => $item) {
+ if (!isset($item['view']) || (empty($item['view']) && $item['view'] !== 0)) {
+ $variables['items'][$delta]['empty'] = TRUE;
+ }
+ else {
+ $field_empty = FALSE;
+ $variables['items'][$delta]['empty'] = FALSE;
+ }
+ }
+
+ $additions = array(
+ 'field_type' => $field['type'],
+ 'field_name' => $field['field_name'],
+ 'field_type_css' => strtr($field['type'], '_', '-'),
+ 'field_name_css' => strtr($field['field_name'], '_', '-'),
+ 'label' => $field['widget']['label'],
+ 'label_display' => isset($field['display_settings']['label']['format']) ? $field['display_settings']['label']['format'] : 'above',
+ 'field_empty' => $field_empty,
+ );
+ $variables = array_merge($variables, $additions);
+
+}
+
+/**
+ * Debugging using hook_content_fieldapi.
+ *
+ * @TODO remove later
+ *
+ * @param $op
+ * @param $field
+ */
+function content_content_fieldapi($op, $field) {
+ if (module_exists('devel')) {
+ //dsm($op);
+ //dsm($field);
+ }
+}
+
+/**
+ * Implementation of hook_content_extra_fields.
+ *
+ * Informations for non-CCK 'node fields' defined in core.
+ */
+function content_content_extra_fields($type_name) {
+ $type = node_get_types('type', $type_name);
+ $extra = array();
+
+ if ($type->has_title) {
+ $extra['title'] = array('label' => $type->title_label, 'weight' => -5);
+ }
+ if ($type->has_body) {
+ $extra['body_field'] = array('label' => $type->body_label, 'weight' => 0, 'view' => 'body');
+ }
+ if (module_exists('locale') && variable_get("language_$type_name", 0)) {
+ $extra['language'] = array('label' => t('Language'), 'weight' => 0);
+ }
+ if (module_exists('taxonomy') && taxonomy_get_vocabularies($type_name)) {
+ $extra['taxonomy'] = array('label' => t('Taxonomy'), 'weight' => -3);
+ }
+ if (module_exists('upload') && variable_get("upload_$type_name", TRUE)) {
+ $extra['attachments'] = array('label' => t('File attachments'), 'weight' => 30, 'view' => 'files');
+ }
+
+ return $extra;
+}
+
+/**
+ * Retrieve the user-defined weight for non-CCK node 'fields'.
+ *
+ * CCK's 'Manage fields' page lets users reorder node fields, including non-CCK
+ * items (body, taxonomy, other hook_nodeapi-added elements by contrib modules...).
+ * Contrib modules that want to have their 'fields' supported need to expose
+ * them with hook_content_extra_fields, and use this function to retrieve the
+ * user-defined weight.
+ *
+ * @param $type_name
+ * The content type name.
+ * @param $pseudo_field_name
+ * The name of the 'field'.
+ * @return
+ * The weight for the 'field', respecting the user settings stored
+ * by content.module.
+ */
+function content_extra_field_weight($type_name, $pseudo_field_name) {
+ $type = content_types($type_name);
+
+ // If we don't have the requested item, this may be because the cached
+ // information for 'extra' fields hasn't been refreshed yet.
+ if (!isset($type['extra'][$pseudo_field_name])) {
+ content_clear_type_cache();
+ }
+
+ if (isset($type['extra'][$pseudo_field_name])) {
+ return $type['extra'][$pseudo_field_name]['weight'];
+ }
+}
diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/content.test ./modules/fields/content.test
--- /dev/null 1969-12-31 19:00:00.000000000 -0500
+++ ./modules/fields/content.test 2008-05-31 11:58:04.000000000 -0400
@@ -0,0 +1,162 @@
+ t('Field API'),
+ 'description' => t('Test Drupal\'s Field API'),
+ 'group' => t('Fields')
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $this->drupalModuleEnable('content');
+ $this->drupalModuleEnable('text');
+ }
+
+ function testCreateField() {
+ // Create a field.
+ $field = new Field($this->randomName(10), 'text');
+ $field_name = $field->field_name;
+ $this->assertIdentical(field_create_field($field), TRUE, 'Create field.');
+
+ // Read it back and verify.
+ $fields = field_get_field(array('field_name' => $field_name));
+ $this->assertIdentical($fields[0]->field_name, $field_name, 'Field created');
+ $this->assertIdentical($fields[0]->module, 'text', 'Module set correctly');
+ $this->assertEqual($fields[0]->required, FALSE, 'required set correctly');
+ $this->assertEqual($fields[0]->shareable, FALSE, 'shareable set correctly');
+ $this->assertEqual($fields[0]->multiple, 0, 'multiple set correctly');
+ $this->assertEqual($fields[0]->db_storage, CONTENT_DB_STORAGE_PER_CONTENT_TYPE, 'db_storage set correctly');
+
+ // Field names must be unique.
+ $this->assertIdentical(field_create_field($field), FALSE, 'Cannot create duplicate field name.');
+
+ // Create another field with non-default values.
+ $field_name = $field->field_name = $this->randomName(10);
+ $field->required = TRUE;
+ $field->shareable = TRUE;
+ $field->multiple = 1;
+ $this->assertIdentical(field_create_field($field), TRUE, 'Create field.');
+
+ // Read it back and verify.
+ $fields = field_get_field(array('field_name' => $field_name));
+ $this->assertIdentical($fields[0]->field_name, $field_name, 'Field created');
+ $this->assertIdentical($fields[0]->module, 'text', 'Module set correctly');
+ $this->assertEqual($fields[0]->required, TRUE, 'required set correctly');
+ $this->assertEqual($fields[0]->shareable, TRUE, 'shareable set correctly');
+ $this->assertEqual($fields[0]->multiple, 1, 'multiple set correctly');
+ $this->assertEqual($fields[0]->db_storage, CONTENT_DB_STORAGE_PER_FIELD, 'db_storage set correctly');
+ }
+
+ function testCreateFieldInstance() {
+ // Create a field.
+ $field = new Field('', 'text');
+ $field_name = $field->field_name = $this->randomName(10);
+ $this->assertIdentical(field_create_field($field), TRUE, 'Create field.');
+
+ // Create a field instance structure. Set some non-default values,
+ // including the text widget's "rows" setting, so we can verify them.
+ $content_type = 'article';
+ $instance = new FieldInstance($field_name, $content_type, 'text_textarea');
+ $instance->widget['weight'] = 12;
+ $label = $instance->widget['label'] = 'label:'. $this->randomName(32);
+ $description = $instance->widget['description'] = 'description:'. $this->randomName(32);
+ $rows = $instance->widget['rows'] = 17;
+
+ // Cannot create an instance on a non-existant content type.
+ $instance->type_name = 'this_content_type_does_not_exist';
+ $this->assertEqual(field_create_instance($instance), FALSE, 'Cannot create an instance on a non-existant content type.');
+
+ // Create the instance.
+ $instance->type_name = $content_type;
+ $this->assertEqual(field_create_instance($instance), TRUE, 'Create instance.');
+
+ // Read it back and verify.
+ $read = $this->_readAndVerifyInstance($instance);
+ $this->assertEqual($read->widget['rows'], $rows, 'widget[rows] matches');
+
+ // TODO: display settings
+
+ // The field instance's database columns exist.
+ foreach ($field->columns as $column => $attrs) {
+ $this->assertIdentical(db_column_exists('content_type_article', $field_name .'_'. $column), TRUE, "Field's column $column exists.");
+ }
+
+ // The field can be saved and loaded.
+ $node->type = $content_type;
+ $node->title = 'title';
+ $field_value[0]['value'] = $this->randomName(64);
+ $node->$field_name = $field_value;
+ node_save($node);
+
+ $newnode = node_load($node->nid);
+ $newfield = $newnode->$field_name;
+ $this->assertIdentical($field_value[0]['value'], $newfield[0]['value'], 'New field is saved and loaded.');
+
+ // The field's label and value is displayed
+ $this->drupalGet('node/'.$node->nid);
+ $this->assertText($label, 'New field label is displayed.');
+ $this->assertText($field_value[0]['value'], 'New field value is displayed.');
+
+ // Field is shown on form, using the widget settings.
+ $web_user = $this->drupalCreateUser(array('edit own article content', 'create article content'));
+ $this->drupalLogin($web_user);
+ $this->drupalGet('node/add/article');
+ $this->assertText($description, 'New field description is shown on form.');
+ $this->assertPattern('@