diff --git a/content.module b/content.module index 4268b86..81722db 100644 --- a/content.module +++ b/content.module @@ -1156,6 +1156,23 @@ function content_database_info($field) { } /** + * Helper function for identifying the storage type for a field. + */ +function content_storage_type($field) { + if ($field['multiple'] > 0) { + return CONTENT_DB_STORAGE_PER_FIELD; + } + else { + require_once drupal_get_path('module', 'content') ."/content_crud.inc"; + $instances = content_field_instance_read(array('field_name' => $field['field_name'])); + if (count($instances) > 1) { + 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. diff --git a/content_admin.inc b/content_admin.inc index dd51def..10322cf 100644 --- a/content_admin.inc +++ b/content_admin.inc @@ -575,35 +575,12 @@ function theme_content_admin_field_add_new_field_widget_type($form) { * Add an existing field to a content type. */ function _content_admin_field_add_existing_submit($form_id, $form_values) { + require_once './'. drupal_get_path('module', 'content') .'/content_crud.inc'; $type = content_types($form_values['type_name']); - $field = content_fields($form_values['field_name']); - $field_types = _content_field_types(); - $field_type = $field_types[$field['type']]; - $columns = module_invoke($field_type['module'], 'field_settings', 'database columns', $field); - - if (is_array($columns) && count($columns)) { - if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE) { - $new_field = $field; - $new_field['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD; - db_query("UPDATE {node_field} SET db_storage = %d WHERE field_name = '%s'", CONTENT_DB_STORAGE_PER_FIELD, $form_values['field_name']); - content_alter_db_field($field, $columns, $new_field, $columns); - } - } - $prior_instance = db_fetch_array(db_query("SELECT * FROM {node_field_instance} WHERE field_name = '%s'", $form_values['field_name'])); - if (!$prior_instance) { - $prior_instance = array(); - $prior_instance['weight'] = 0; - $prior_instance['label'] = $form_values['field_name']; - $prior_instance['widget_type'] = ''; - $prior_instance['widget_settings'] = ''; - $prior_instance['display_settings'] = ''; - $prior_instance['description'] = ''; - } - db_query("INSERT INTO {node_field_instance} (field_name, type_name, weight, label, widget_type, widget_settings, display_settings, description) VALUES ('%s', '%s', %d, '%s', '%s', '%s', '%s', '%s')", $form_values['field_name'], $form_values['type_name'], $prior_instance['weight'], $prior_instance['label'], $prior_instance['widget_type'], $prior_instance['widget_settings'], $prior_instance['display_settings'], $prior_instance['description']); + content_field_instance_create($form_values); drupal_set_message(t('Added field %label.', array('%label' => $prior_instance['label']))); - content_clear_type_cache(); return 'admin/content/types/'. $type['url_str'] .'/fields'; } @@ -628,16 +605,14 @@ function _content_admin_field_add_new_validate($form_id, $form_values) { * Create a new field for a content type. */ function _content_admin_field_add_new_submit($form_id, $form_values) { - // Find a valid, computer-friendly field name. - - $fields = content_fields(); + require_once './'. drupal_get_path('module', 'content') .'/content_crud.inc'; $type = content_types($form_values['type_name']); - - // Accept field name from programmed submissions if valid and it doesn't already exist. - if ($form_values['field_name']) { - $field_name = $form_values['field_name']; - } - else { + $field = array( + 'field_name' => $form_values['field_name'], + 'label' => $form_values['label'], + 'type_name' => $form_values['type_name'], + ); + if (empty($form_values['field_name'])) { $field_name = trim($form_values['label']); $field_name = drupal_strtolower($field_name); $field_name = str_replace(array(' ', '-'), '_', $field_name); @@ -651,24 +626,15 @@ function _content_admin_field_add_new_submit($form_id, $form_values) { } while (isset($fields[$new_name])); $field_name = $new_name; } + $field['field_name'] = $field_name; } + $widget = explode('-', $form_values['field_widget_type']); + $field['type'] = $widget[0]; + $field['widget_type'] = $widget[1]; - $field_widget_type = explode('-', $form_values['field_widget_type']); - db_query("INSERT INTO {node_field} (field_name, type, global_settings, required, multiple, db_storage) VALUES ('%s', '%s', '%s', %d, %d, %d)", $field_name, $field_widget_type[0], serialize(array()), 0, 0, CONTENT_DB_STORAGE_PER_CONTENT_TYPE); - db_query("INSERT INTO {node_field_instance} (field_name, type_name, weight, label, widget_type, widget_settings, display_settings, description) VALUES ('%s', '%s', %d, '%s', '%s', '%s', '%s', '%s')", $field_name, $form_values['type_name'], 0, $form_values['label'], $field_widget_type[1], serialize(array()), serialize(array()), ''); - + content_field_instance_create($field); content_clear_type_cache(); - // Create new database columns as necessary. - $field_types = _content_field_types(); - $field_type = $field_types[$field_widget_type[0]]; - $field = content_fields($field_name); - - $columns = module_invoke($field_type['module'], 'field_settings', 'database columns', $field); - if (is_array($columns) && count($columns)) { - content_alter_db_field(array(), array(), $field, $columns); - } - drupal_set_message(t('Created field %label.', array('%label' => $form_values['label']))); return 'admin/content/types/'. $type['url_str'] .'/fields/'. $field_name; } @@ -710,7 +676,7 @@ function _content_admin_field_remove_submit($form_id, $form_values) { if ($type && $field && $form_values['confirm']) { include_once('./'. drupal_get_path('module', 'content') .'/content_crud.inc'); - content_field_instance_delete($form_values); + content_field_instance_delete($field['field_name'], $field['type_name']); drupal_set_message(t('Removed field %field from %type.', array('%field' => $field['widget']['label'], '%type' => $type['name']))); content_clear_type_cache(); @@ -900,10 +866,6 @@ function _content_admin_field($type_name, $field_name) { '#type' => 'value', '#value' => $field['type'], ); - $form['module'] = array( - '#type' => 'value', - '#value' => implode(', ', array_unique(array($field_type['module'], $widget_type['module']))), - ); return $form; } @@ -1012,67 +974,45 @@ function _content_admin_field_validate($form_id, $form_values, $form) { * Save a field's settings after editing. */ function _content_admin_field_submit($form_id, $form_values) { + require_once './'. drupal_get_path('module', 'content') .'/content_crud.inc'; $type = content_types($form_values['type_name']); - $field = $type['fields'][$form_values['field_name']]; - $field_types = _content_field_types(); - $field_type = $field_types[$field['type']]; - $widget_types = _content_widget_types(); - $widget_type = $widget_types[$form_values['widget_type']]; - - // If content.module is handling the default value, - // initialize $widget_settings with default values, - if (content_handle('widget', 'default value', $field) == CONTENT_CALLBACK_DEFAULT) { - $widget_settings = array( - 'default_value' => $form_values['default_value'], - 'default_value_php' => $form_values['default_value_php'], - ); - } - $setting_names = module_invoke($widget_type['module'], 'widget_settings', 'save', $field); - if (is_array($setting_names)) { - foreach ($setting_names as $setting) { - $widget_settings[$setting] = $form_values[$setting]; - } - } - $field_settings = array(); - $setting_names = module_invoke($field_type['module'], 'field_settings', 'save', $field); - if (is_array($setting_names)) { - foreach ($setting_names as $setting) { - $field_settings[$setting] = $form_values[$setting]; - } - } - - $prev_field = $field; - $prev_columns = module_invoke($field_type['module'], 'field_settings', 'database columns', $field); + content_field_instance_update($form_values); - db_query("UPDATE {node_field_instance} SET weight = %d, label = '%s', widget_type = '%s', widget_settings = '%s', description = '%s' WHERE type_name = '%s' AND field_name = '%s'", $form_values['weight'], $form_values['label'], $form_values['widget_type'], serialize($widget_settings), $form_values['description'], $form_values['type_name'], $form_values['field_name']); + drupal_set_message(t('Saved field %field.', array('%field' => $form_values['label']))); + return 'admin/content/types/'. $type['url_str'] .'/fields'; +} - if ($form_values['multiple']) { - $field['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD; +/** + * Content Schema Alter + * + * Alter the database schema. + * + * TODO figure out an API-safe way to use batching to update the nodes that + * will be affected by this change so the node_save() hooks will fire. + * + */ +function content_alter_schema($previous_field, $new_field) { + $type = !empty($new_field) ? $new_field['type'] : $previous_field['type']; + $field_types = _content_field_types(); + $field_type = $field_types[$type]; + if (empty($previous_field)) { + $previous_columns = array(); } else { - $instances = db_result(db_query("SELECT COUNT(*) FROM {node_field_instance} WHERE field_name = '%s'", $form_values['field_name'])); - if ($instances == 1) { - $field['db_storage'] = CONTENT_DB_STORAGE_PER_CONTENT_TYPE; - } + $previous_columns = module_invoke($field_type['module'], 'field_settings', 'database columns', $previous_field); } - db_query("UPDATE {node_field} SET required = %d, multiple = %d, global_settings = '%s', db_storage = %d WHERE field_name = '%s'", $form_values['required'], $form_values['multiple'], serialize($field_settings), $field['db_storage'], $form_values['field_name']); - drupal_set_message(t('Saved field %field.', array('%field' => $form_values['label']))); content_clear_type_cache(); - $new_field = content_fields($form_values['field_name']); - $new_columns = module_invoke($field_type['module'], 'field_settings', 'database columns', $new_field); - - if (!isset($prev_columns)) { - $prev_columns = array(); - } - if (!isset($new_columns)) { + if (empty($new_field)) { $new_columns = array(); } - content_alter_db_field($prev_field, $prev_columns, $new_field, $new_columns); + else { + $new_columns = module_invoke($field_type['module'], 'field_settings', 'database columns', $new_field); + } - return 'admin/content/types/'. $type['url_str'] .'/fields'; + content_alter_db_field($previous_field, $previous_columns, $new_field, $new_columns); } /** diff --git a/content_crud.inc b/content_crud.inc index 8d45925..edfdc9b 100644 --- a/content_crud.inc +++ b/content_crud.inc @@ -4,9 +4,103 @@ /** * @file * Create/Read/Update/Delete functions for CCK-defined object types. + * + * The content module field API will allow $field arguments to + * be input either in the field => widget nested array that is used + * by the content module, or in flattened $form_values arrays, by + * converting flattened arrays to the nested format. + * + * A hook_content_fieldapi() is available for each field instance action, + * and each hook receives the nested field => widget array as an argument. + * + * The hook_content_fieldapi() $ops include: + * + * - create instance + * - read instance + * - update instance + * - delete instance + * + * Another function, content_module_delete($module) will clean up + * after a module that has been deleted by removing all data and + * settings information that was created by that module. */ /** + * Create an array of default values for a field type. + */ +function content_field_default_values($field_type) { + $field_types = _content_field_types(); + $module = $field_types[$field_type]['module']; + + $field = array( + 'module' => $module, + 'type' => $field_type, + 'active' => 0, + ); + + if (module_exists($module)) { + $field['active'] = 1; + } + + $field['columns'] = module_invoke($module, 'field_settings', 'database columns', $field); + // Ensure columns always default to NULL values. + foreach ((array) $field['columns'] as $column_name => $column) { + $field['columns'][$column_name]['not null'] = FALSE; + } + + $field['required'] = 0; + $field['multiple'] = 0; + $field['db_storage'] = CONTENT_DB_STORAGE_PER_CONTENT_TYPE; + + // Make sure field settings all have an index in the array. + $setting_names = module_invoke($module, 'field_settings', 'save', $field); + if (is_array($setting_names)) { + foreach ($setting_names as $setting) { + $field[$setting] = NULL; + } + } + return $field; +} + +/** + * Create an array of default values for a field instance. + */ +function content_instance_default_values($field_name, $type_name, $widget_type) { + $widget_types = _content_widget_types(); + $module = $widget_types[$widget_type]['module']; + + $widget = array( + 'field_name' => $field_name, + 'type_name' => $type_name, + 'weight' => 0, + 'label' => $field_name, + 'description' => '', + 'widget_type' => $widget_type, + 'widget_module' => $module, + 'display_settings' => array(), + 'widget_settings' => array(), + ); + + if (module_exists($module)) { + $widget['widget_active'] = 1; + } + + $settings_names = array_merge(array('label'), array_keys(_content_admin_display_contexts())); + $widget['display_settings'] = array(); + foreach ($settings_names as $name) { + $widget['display_settings'][$name]['format'] = ($name == 'label') ? 'above' : 'default'; + } + + // Make sure widget settings all have an index in the array. + $settings_names = module_invoke($module, 'widget_settings', 'save', $widget); + $widget['widget_settings'] = array(); + foreach ((array) $settings_names as $name) { + $widget['widget_settings'][$name] = NULL; + } + return $widget; +} + +/** * Expand field info to create field => widget info. */ function content_field_instance_expand($field) { @@ -16,12 +110,12 @@ function content_field_instance_expand($field) { $field['widget'] = !empty($field['widget_settings']) ? $field['widget_settings'] : array(); $field['widget']['label'] = !empty($field['label']) ? $field['label'] : $field['field_name']; $field['widget']['weight'] = !empty($field['weight']) ? $field['weight'] : NULL; - $field['widget']['description'] = !empty($field['description']) ? $field['description'] : ''; + $field['widget']['description'] = !empty($field['description']) ? $field['description'] : NULL; if (!empty($field['widget_type'])) { $field['widget']['type'] = $field['widget_type']; $widget_types = _content_widget_types(); - $field['widget']['module'] = isset($widget_types[$field['widget_type']]['module']) ? $widget_types[$field['widget_type']]['module'] : $field['widget_module']; + $field['widget']['module'] = $widget_types[$field['widget_type']]['module']; } elseif (!empty($field['widget_module'])) { $field['widget']['module'] = $field['widget_module']; @@ -37,7 +131,7 @@ function content_field_instance_expand($field) { // If content.module is handling the default value, // initialize $widget_settings with default values, if (isset($field['default_value']) && isset($field['default_value_php']) && - content_callback('widget', 'default value', $field) == CONTENT_CALLBACK_DEFAULT) { + content_handle('widget', 'default value', $field) == CONTENT_CALLBACK_DEFAULT) { $field['widget']['default_value'] = !empty($field['default_value']) ? $field['default_value'] : NULL; $field['widget']['default_value_php'] = !empty($field['default_value_php']) ? $field['default_value_php'] : NULL; unset($field['default_value']); @@ -81,24 +175,364 @@ function content_field_instance_collapse($field) { } /** - * Rebuild content type information from node tables. + * Create a new field instance. + * + * @param $field + * An array of properties to create the field with, input either in + * the field => widget format used by the content module or as an + * array of form values. * - * Used to update CCK tables that might have missed changes made when CCK was disabled. - * Called by hook_form_alter() on system modules page whenever CCK is enabled. + * Required values: + * - field_name, the name of the field to be created + * - type_name, the content type of the instance to be created + * + * If there is no prior instance to create this from, we also need: + * - type, the type of field to create + * - widget_type, the type of widget to use */ -function content_types_rebuild() { - $db_types = content_types(); +function content_field_instance_create($field) { + include_once('./'. drupal_get_path('module', 'content') .'/content_admin.inc'); + + $form_values = $field; + $field = content_field_instance_expand($field); + + // If there are prior instances, fill out missing values from the prior values, + // otherwise get missing values from default values. + $prior_instances = content_field_instance_read(array('field_name' => $field['field_name'])); + if (!empty($prior_instances) && is_array($prior_instances)) { + $prev_field = content_field_instance_expand($prior_instances[0]); + + // Weight, label, and description may have been forced into the $field + // by content_field_instance_expand(). If there is a previous instance to + // get these values from and there was no value supplied originally, use + // the previous value. + $field['widget']['weight'] = isset($form_values['weight']) ? $form_values['weight'] : $prev_field['widget']['weight']; + $field['widget']['label'] = isset($form_values['label']) ? $form_values['label'] : $prev_field['widget']['label']; + $field['widget']['description'] = isset($form_values['description']) ? $form_values['description'] : $prev_field['widget']['description']; + } + else { + $prev_field = array('widget' => array()); + } - $result = db_query("SELECT type_name FROM {node_field_instance}"); - while ($type = db_fetch_array($result)) { - $field_types[] = $type['type_name']; + // If we have a field type, we can build default values for this field type. + $default_values = array('widget' => array()); + if (isset($field['type'])) { + $default_values = content_field_default_values($field['type']); + $default_instance_values = content_instance_default_values($field['field_name'], $field['type_name'], $field['widget']['type']); + $default_values = content_field_instance_expand(array_merge($default_values, $default_instance_values)); } - foreach ($db_types as $content_type) { - // Find content types that are missing the content table and add it - content_type_create((object)array('type' => $content_type['type'])); + // Merge default values, previous values, and current values to create + // a complete field array. + $widget = array_merge($default_values['widget'], $prev_field['widget'], $field['widget']); + $field = array_merge($default_values, $prev_field, $field); + $field['widget'] = $widget; + + // Make sure we know what module to invoke for field info. + if (empty($field['module']) && !empty($field['type'])) { + $field_types = _content_field_types(); + $field['module'] = $field_types[$field['type']]['module']; } - content_clear_type_cache(); + + // The storage type may need to be updated. + $field['db_storage'] = content_storage_type($field); + + // Get a fresh copy of the column information whenever a field is created. + $field['columns'] = (array) module_invoke($field['module'], 'field_settings', 'database columns', $field); + + if (empty($prev_field['widget']) || $prior_instances < 1) { + // If this is the first instance, create the field. + $field['db_storage'] = $field['multiple'] > 0 ? CONTENT_DB_STORAGE_PER_FIELD : CONTENT_DB_STORAGE_PER_CONTENT_TYPE; + _content_field_write($field, 'create'); + } + elseif (!empty($prev_field['widget']) && $prev_field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE && count($prior_instances) > 0) { + // If the database storage has changed, update the field and previous instances. + $field['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD; + + foreach ($prior_instances as $instance) { + $new_instance = $instance; + $new_instance['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD; + + // Invoke hook_content_fieldapi(). + module_invoke_all('content_fieldapi', 'update instance', $new_instance); + + content_alter_schema($instance, $new_instance); + } + $prev_field['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD; + } + + // Invoke hook_content_fieldapi(). + module_invoke_all('content_fieldapi', 'create instance', $field); + + // Update the field and the instance with the latest values. + _content_field_write($field, 'update'); + _content_field_instance_write($field, 'create'); + + content_alter_schema(empty($prev_field['widget']) ? array() : $prev_field, $field); + + content_clear_type_cache(TRUE); + menu_rebuild(TRUE); + + return $field; +} + +/** + * Update an existing field instance. + * + * @param $field + * An array of properties to update the field with, input either in + * the field => widget format used by the content module or as an + * array of form values. + */ +function content_field_instance_update($field) { + include_once('./'. drupal_get_path('module', 'content') .'/content_admin.inc'); + + // Get the previous value from the table. + $previous = content_field_instance_read(array('field_name' => $field['field_name'], 'type_name' => $field['type_name'])); + $prev_field = array_pop($previous); + + $field = content_field_instance_expand($field); + + // Create a complete field array by merging the previous and current values, + // letting the current values overwrite the previous ones. + $widget = array_merge($prev_field['widget'], $field['widget']); + $field = array_merge($prev_field, $field); + $field['widget'] = $widget; + + // Make sure we know what module to invoke for field info. + if (empty($field['module']) && !empty($field['type'])) { + $field_types = _content_field_types(); + $field['module'] = $field_types[$field['type']]['module']; + } + + // The storage type may need to be updated. + $field['db_storage'] = content_storage_type($field); + + // Changes in field values may affect columns, or column + // information may have changed, get a fresh copy. + $field['columns'] = (array) module_invoke($field['module'], 'field_settings', 'database columns', $field); + + // If the database storage has changed, update the field and previous instances. + $prior_instances = content_field_instance_read(array('field_name' => $field['field_name'])); + + if ($prev_field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE && count($prior_instances) > 1) { + + // Update the field's data storage. + $field['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD; + + // Update the schema for prior instances to adapt to the change in db storage. + foreach ($prior_instances as $instance) { + if ($instance['type_name'] != $field['type_name']) { + $new_instance = $instance; + $new_instance['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD; + + // Invoke hook_content_fieldapi(). + module_invoke_all('content_fieldapi', 'update instance', $new_instance); + + content_alter_schema($instance, $new_instance); + } + } + } + + // Invoke hook_content_fieldapi(). + module_invoke_all('content_fieldapi', 'update instance', $field); + + // Update the field and the instance with the latest values. + _content_field_write($field, 'update'); + _content_field_instance_write($field, 'update'); + + content_alter_schema($prev_field, $field); + + content_clear_type_cache(TRUE); + + return $field; +} + +/** + * Write a field record. + * + * @param $field + * The field array to process. + */ +function _content_field_write($field, $op = 'update') { + // Rearrange the data to create the global_settings array. + $field['global_settings'] = array(); + // Make sure we know what module to invoke for field info. + if (empty($field['module']) && !empty($field['type'])) { + $field_types = _content_field_types(); + $field['module'] = $field_types[$field['type']]['module']; + } + $setting_names = module_invoke($field['module'], 'field_settings', 'save', $field); + if (is_array($setting_names)) { + foreach ($setting_names as $setting) { + $field['global_settings'][$setting] = isset($field[$setting]) ? $field[$setting] : ''; + unset($field[$setting]); + } + } + // 'columns' is a reserved word in MySQL4, so our column is named 'db_columns'. + $field['db_columns'] = $field['columns']; + + switch ($op) { + case 'create': + db_query("INSERT INTO {node_field} (field_name, type, global_settings, required, multiple, db_storage) VALUES ('%s', '%s', '%s', %d, %d, %d)", $field['field_name'], $field['type'], serialize($field['global_settings']), $field['required'], $field['multiple'], $field['db_storage']); + break; + case 'update': + db_query("UPDATE {node_field} SET type = '%s', global_settings = '%s', + required = %d, multiple = %d, db_storage = %d WHERE field_name = '%s'", $field['type'], serialize($field['global_settings']), $field['required'], $field['multiple'], $field['db_storage'], $field['field_name']); + break; + } + unset($field['db_columns']); + return $field; +} + +/** + * Write a field instance record. + * + * @param $field + * The field array to process. + */ +function _content_field_instance_write($field, $op = 'update') { + // Collapse the field => widget format. + $field = content_field_instance_collapse($field); + + // Rearrange the data to create the widget_settings array. + $setting_names = module_invoke($field['widget_module'], 'widget_settings', 'save', $field); + if (is_array($setting_names)) { + foreach ($setting_names as $setting) { + $field['widget_settings'][$setting] = isset($field[$setting]) ? $field[$setting] : ''; + unset($field[$setting]); + } + } + + switch ($op) { + case 'create': + db_query("INSERT INTO {node_field_instance} (field_name, type_name, weight, label, widget_type, widget_settings, display_settings, description) VALUES ('%s','%s',%d,'%s','%s','%s','%s','%s')", $field['field_name'], $field['type_name'], $field['weight'], $field['label'], $field['widget_type'], serialize($field['widget_settings']), serialize($field['display_settings']), $field['description']); + break; + case 'update': + db_query("UPDATE {node_field_instance} SET weight = %d, label = '%s', widget_type = '%s', widget_settings = '%s', display_settings = '%s', description = '%s' WHERE field_name = '%s' AND type_name = '%s'", $field['weight'], $field['label'], $field['widget_type'], serialize($field['widget_settings']), serialize($field['display_settings']), $field['description'], $field['field_name'], $field['type_name']); + break; + } + return $field; +} + +/** + * Load a field instance. + * + * @param $param + * An array of properties to use in selecting a field instance. Valid keys: + * - 'type_name' - The name of the content type in which the instance exists. + * - 'field_name' - The name of the field whose instance is to be loaded. + * if NULL, all instances will be returned. + * @return + * The field arrays. + */ +function content_field_instance_read($param = NULL) { + if (is_array($param)) { + // Turn the conditions into a query. + foreach ($param as $key => $value) { + $cond[] = 'nfi.'. db_escape_string($key) ." = '%s'"; + $args[] = $value; + } + } + + if (count($cond)) { + $cond = implode(' AND ', $cond); + } + $db_result = db_query("SELECT * FROM {node_field_instance} nfi ". + " JOIN {node_field} nf ON nfi.field_name = nf.field_name ". + " WHERE ". $cond ." ORDER BY nfi.weight ASC, nfi.label ASC", $args); + + $fields = array(); + while ($instance = db_fetch_array($db_result)) { + // 'columns' is a reserved word in MySQL4, so our column is named 'db_columns'. + $instance['columns'] = (array) (!empty($instance['db_columns']) ? unserialize($instance['db_columns']) : array()); + unset($instance['db_columns']); + $instance['global_settings'] = (array) (!empty($instance['global_settings']) ? unserialize($instance['global_settings']) : array()); + + foreach ($instance['global_settings'] as $key => $value) { + $instance[$key] = $value; + } + unset($instance['global_settings']); + + if (!empty($instance['widget_settings'])) { + $instance['widget_settings'] = (array) unserialize($instance['widget_settings']); + } + else { + $instance['widget_settings'] = array(); + } + if (!empty($instance['display_settings'])) { + $instance['display_settings'] = (array) unserialize($instance['display_settings']); + } + else { + $instance['display_settings'] = array(); + } + + $field = content_field_instance_expand($instance); + + // Invoke hook_content_fieldapi(). + module_invoke_all('content_fieldapi', 'read instance', $field); + $fields[] = $field; + } + return $fields; +} + +/** + * Delete an existing field instance. + */ +function content_field_instance_delete($field_name, $type_name) { + include_once('./'. drupal_get_path('module', 'content') .'/content_admin.inc'); + + // Get the previous field value. + $field = array_pop(content_field_instance_read(array('field_name' => $field_name, 'type_name' => $type_name))); + + // Invoke hook_content_fieldapi(). + module_invoke_all('content_fieldapi', 'delete instance', $field); + + db_query("DELETE FROM {node_field_instance} WHERE field_name = '%s' AND type_name = '%s'", $field['field_name'], $field['type_name']); + + // If no instances remain, delete the field entirely. + $instances = content_field_instance_read(array('field_name' => $field_name)); + if (sizeof($instances) < 1) { + // Remove the fields from the database + content_alter_schema($field, array()); + + db_query("DELETE FROM {node_field} WHERE field_name = '%s'", $field['field_name']); + + // Deleted fields require a menu rebuild to remove all the field paths. + menu_rebuild(TRUE); + } + + // If only one instance remains, we may need to change the database + // representation for this field. + if (sizeof($instances) == 1 && !($field['multiple'])) { + + // Multiple-valued fields are always stored per-field-type. + $instance = $instances[0]; + $new_instance = $instance; + $new_instance['db_storage'] = CONTENT_DB_STORAGE_PER_CONTENT_TYPE; + _content_field_write($new_instance, 'update'); + + content_alter_schema($instance, $new_instance); + } + + content_clear_type_cache(TRUE); + return $field; +} + +/** + * Delete all data related to a module. + * + * @param string $module + */ +function content_module_delete($module) { + // Delete the field data. + $results = db_query("SELECT field_name, type_name FROM {node_field_instance} WHERE widget_module = '%s'", $module); + while ($field = db_fetch_array($results)) { + content_field_instance_delete($field['field_name'], $field['type_name']); + } + // Force the caches and static arrays to update to the new info. + _content_type_info(TRUE, TRUE); + } /** @@ -106,6 +540,10 @@ function content_types_rebuild() { * * @param $info * value supplied by hook_node_type() + * + * node_get_types() is still missing the new type at this point, so no + * use to call content_clear_type_cache() or menu_rebuild() here. Instead + * we set a flag to make sure it gets rebuilt on next page load. */ function content_type_create($info) { content_clear_type_cache(); @@ -118,8 +556,7 @@ function content_type_create($info) { db_query("CREATE TABLE {". $table ."} ( vid int unsigned NOT NULL default '0', nid int unsigned NOT NULL default '0', - PRIMARY KEY (vid), - KEY nid (nid) + PRIMARY KEY (vid) ) /*!40100 DEFAULT CHARACTER SET utf8 */"); break; @@ -129,7 +566,6 @@ function content_type_create($info) { nid int_unsigned NOT NULL default '0', PRIMARY KEY (vid) )"); - db_query("CREATE INDEX {". $table ."}_nid_idx ON {". $table ."}(nid)"); break; } drupal_set_message(t('The content fields table %name has been created.', array('%name' => $table))); @@ -144,7 +580,7 @@ function content_type_create($info) { */ function content_type_update($info) { if (!empty($info->old_type) && $info->old_type != $info->type) { - // rename the content type in all fields that use changed content type. + // Rename the content type in all fields that use changed content type. db_query("UPDATE {node_field_instance} SET type_name='%s' WHERE type_name='%s'", array($info->type, $info->old_type)); // Rename the content fields table to match new content type name. @@ -152,21 +588,22 @@ function content_type_update($info) { $old_name = _content_tablename($old_type['type'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE); $new_name = _content_tablename($info->type, CONTENT_DB_STORAGE_PER_CONTENT_TYPE); if (db_table_exists($old_name)) { - switch ($GLOBALS['db_type']) { - case 'mysql': - case 'mysqli': - db_query("RENAME TABLE {". $old_name ."} TO {". $new_name ."}"); - break; - - case 'pgsql': - db_query("ALTER TABLE {". $old_name ."} RENAME TO {". $new_name ."}"); - break; - } - drupal_set_message(t('Content fields table %old_name has been renamed to %new_name and field instances have been updated.', array('%old_name' => $old_name, '%new_name' => $new_name))); + $ret = array(); + db_rename_table($ret, $old_name, $new_name); + watchdog('content', 'Content fields table %old_name has been renamed to %new_name and field instances have been updated.', array( + '%old_name' => $old_name, '%new_name' => $new_name)); + } + + // Rename the variable storing weights for non-CCK fields. + if ($extra = variable_get('content_extra_weights_'. $info->old_type, array())) { + variable_set('content_extra_weights_'. $info->type, $extra); + variable_del('content_extra_weights_'. $info->old_type); } } - // reset all content type info. + + // Reset all content type info and alter the menu paths. content_clear_type_cache(); + menu_rebuild(); } /** @@ -176,176 +613,16 @@ function content_type_update($info) { * value supplied by hook_node_type() */ function content_type_delete($info) { - // Don't delete data for content-type defined by disabled modules. - if (!empty($info->disabled)) { - return; + $fields = content_field_instance_read(array('type_name' => $info->type)); + foreach ($fields as $field) { + content_field_instance_delete($field['field_name'], $info->type); } - - $type = content_types($info->type); - - foreach ($type['fields'] as $field) { - content_field_instance_delete(array('type_name' => $info->type, 'field_name' => $field['field_name'])); - } - $table = _content_tablename($type['type'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE); + $table = _content_tablename($info->type, CONTENT_DB_STORAGE_PER_CONTENT_TYPE); if (db_table_exists($table)) { + $ret = array(); db_query('DROP TABLE {'. $table .'}'); - drupal_set_message(t('The content fields table %name has been deleted.', array('%name' => $table))); + watchdog('content', 'The content fields table %name has been deleted.', array('%name' => $table)); } - // Reset all content type info. - content_clear_type_cache(); -} - - -/** - * Create a new field. - * - * Any call to this function *must* be immediately followed by a call to - * content_field_instance_create(), or the database could be left in an - * inconsistent state. - * - * @param $properties - * An array of properties to load the field with. Valid keys: - * - '' - - * @return - * The ID of the newly-created field. - */ -function content_field_create($properties) { - // TODO -} - -/** - * Load a field. - * - * @param $properties - * An array of properties to use in selecting a field. Valid keys: - * - '' - - * @return - * The field array. - */ -function content_field_read($properties) { - // TODO -} - -/** - * Update an existing field. - * - * @param $properties - * An array of properties to set in the field. Valid keys: - * - '' - - * @return - * The number of fields updated. - */ -function content_field_update($properties) { - // TODO -} - -/** - * Delete an existing field. - * - * @param $properties - * An array of properties to use in selecting a field. Valid keys: - * - 'field_name' - The name of the field to be deleted. - * @return - * The number of fields deleted. - */ -function content_field_delete($properties) { - $result = db_query("SELECT type_name FROM {node_field_instance} WHERE field_name = '%s'", $properties['field_name']); - $type_names = array(); - while ($type = db_fetch_array($result)) { - $type_names[] = $type['type_name']; - } - foreach ($type_names as $type_name) { - content_field_instance_delete(array('type_name' => $type_name, 'field_name' => $properties['field_name'])); - } - return (count($type_names) ? 1 : 0); -} - - -/** - * Create a new field instance. - * - * @param $properties - * An array of properties to load the field instance with. Valid keys: - * - '' - - * @return - * The ID of the newly-created field instance. - */ -function content_field_instance_create($properties) { - // TODO -} - -/** - * Load a field instance. - * - * @param $properties - * An array of properties to use in selecting a field instance. Valid keys: - * - 'type_name' - The name of the content type in which the instance exists. - * - 'field_name' - The name of the field whose instance is to be loaded. - * @return - * The field instance array. - */ -function content_field_instance_read($properties) { - // TODO -} - -/** - * Update an existing field instance. - * - * @param $properties - * An array of properties to set in the field instance. Valid keys: - * - '' - - * @return - * The number of field instance updated. - */ -function content_field_instance_update($properties) { - // TODO -} - -/** - * Delete an existing field instance. - * - * @param $properties - * An array of properties to use in selecting a field instance. Valid keys: - * - 'type_name' - The name of the content type in which the instance exists. - * - 'field_name' - The name of the field whose instance is to be deleted. - * @return - * The number of field instances deleted. - */ -function content_field_instance_delete($properties) { - $number_deleted = db_query("DELETE FROM {node_field_instance} WHERE type_name = '%s' AND field_name = '%s'", $properties['type_name'], $properties['field_name']); - - $type = content_types($properties['type_name']); - $field = $type['fields'][$properties['field_name']]; - $field_types = _content_field_types(); - $field_type = $field_types[$field['type']]; - $columns = module_invoke($field_type['module'], 'field_settings', 'database columns', $field); - - $instances = db_result(db_query("SELECT COUNT(*) FROM {node_field_instance} WHERE field_name = '%s'", $properties['field_name'])); - - // If only one instance remains, we may need to change the database - // representation for this field. - if ($instances == 1) { - if (!($field['multiple'])) { - // Multiple-valued fields are always stored per-content-type. - if (is_array($columns) && count($columns)) { - $new_field = $field; - $new_field['db_storage'] = CONTENT_DB_STORAGE_PER_CONTENT_TYPE; - db_query("UPDATE {node_field} SET db_storage = %d WHERE field_name = '%s'", CONTENT_DB_STORAGE_PER_CONTENT_TYPE, $properties['field_name']); - content_alter_db_field($field, $columns, $new_field, $columns); - } - } - } - - // If no instances remain, delete the field entirely. - elseif ($instances == 0) { - if (is_array($columns) && count($columns)) { - content_alter_db_field($field, $columns, array(), array()); - } - db_query("DELETE FROM {node_field} WHERE field_name = '%s'", $properties['field_name']); - } - - content_clear_type_cache(); - - return $number_deleted; + menu_rebuild(TRUE); }