My module URL field serializes an array of attributes in url_field_presave(). Things work great if an entity with an URL field that has default values is saved via the UI.

Relevant module code:

function url_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
  foreach ($entities as $id => $entity) {
    foreach ($items[$id] as $delta => &$item) {
      if (empty($item['attributes'])) {
        $item['attributes'] = array();
      }
      else {
        $item['attributes'] = unserialize($item['attributes']);
      }
    }
  }
}

function url_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
  foreach ($items as $delta => &$item) {
    ...
    // Serialize the attributes array.
    $item['attributes'] = !empty($item['attributes']) ? serialize($item['attributes']) : NULL;
  }
}

function url_field_widget_form($form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  ...
  $element['attributes'] = array(
    '#type' => 'value',
    '#value' => !empty($items[$delta]['attributes']) ? $items[$delta]['attributes'] : array(),
  );

  return $element;
}

The problem is that when I go to save my field's settings using the field UI, a default value is saved into $instance['default_value'] in the following format:

array (
  0 => 
  array (
    'value' => 'http://example.com/',
    'title' => '',
    'attributes' => 
    array (
    ),
  ),
)

url_field_presave() is not run on the default value $items before it is saved. Interestingly, the field settings does run hook_field_validate. The correct format of the default value should be the following:

array (
  0 => 
  array (
    'value' => 'http://example.com/',
    'title' => '',
    'attributes' => NULL,
  ),
)

And so now whenever an entity is saved programmatically, the only field hooks that get invoked are the presave hooks via field_attach_presave (which at that point $items is still empty), and then in field_default_insert() (via field_attach_insert) the default value is added, as is, right before being saved by the field storage engine. Obviously, the SQL storage cannot save a PHP array into a text database field without it being serialized first, so we can a PDO exception:

PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')' at line 1: INSERT INTO {field_data_field_google_plus_url} (entity_type, entity_id, revision_id, bundle, delta, language, field_google_plus_url_value, field_google_plus_url_title, field_google_plus_url_attributes) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6, :db_insert_placeholder_7, ); Array ( [:db_insert_placeholder_0] => user [:db_insert_placeholder_1] => 137 [:db_insert_placeholder_2] => 137 [:db_insert_placeholder_3] => user [:db_insert_placeholder_4] => 0 [:db_insert_placeholder_5] => und [:db_insert_placeholder_6] => http://example.com/ [:db_insert_placeholder_7] => ) in field_sql_storage_field_storage_write() (line 448 of /home/dave/Dropbox/Projects/drupal7dev/modules/field/modules/field_sql_storage/field_sql_storage.module).

Steps to reproduce

  1. Install and enable the URL field module.
  2. Add an URL field to the user entity with a default value of http://example.com/.
  3. Run the following PHP code:
      $new_user = array(
        'name' => 'omg-another-field-bug',
        'pass' => user_password(),
        'mail' => 'omg-another-field-bug@example.com',
        'init' => 'omg-another-field-bug@example.com',
        'status' => 1,
        'access' => REQUEST_TIME,
        'roles' => array(),
      );
      $account = user_save(NULL, $new_user);
    
  4. Proposed resolution

    Have field_ui_field_edit_form_submit() run the field-level hook_field_presave() on its version of $items before saving the default value in the instance settings.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Dave Reid’s picture

Status: Active » Needs review
FileSize
769 bytes

Likely this needs to be confirmed in Drupal 8 as well. Patch against D7 attached. Should also need tests.

Dave Reid’s picture

Version: 7.x-dev » 8.x-dev
FileSize
754 bytes

Patch without tests against D8.

Status: Needs review » Needs work

The last submitted patch, 1899498-field-default-value-invoke-presave.patch, failed testing.

Dave Reid’s picture

Status: Needs work » Needs review
FileSize
2.73 KB

Interesting, I'm wondering how the call to hook_field_validate() works in field_ui_field_edit_form_validate(). I'm guessing we don't have coverage for taxonomy_field_validate() being used in a default value?

Status: Needs review » Needs work

The last submitted patch, 1899498-field-default-value-invoke-presave.patch, failed testing.

Dave Reid’s picture

Status: Needs work » Needs review
FileSize
2.74 KB

Re-rolled for HEAD.

Status: Needs review » Needs work

The last submitted patch, 1899498-field-default-value-invoke-presave.patch, failed testing.

johnv’s picture

Marked (older issue , but without patches) #1944678: On field settings form, hook_field_load() and hook_field_presave() are not called. as a duplicate of this one.
Encountered this problem when developing the office_hours module.

johnv’s picture

johnv’s picture

@Dave, the initial post contains hook_field_load(). It is not in one of the patches.
In the settings form, both hook_field_load() and hook_field_presave() are not called. IMO these are a couple.

torotil’s picture

Version: 8.x-dev » 7.x-dev
Status: Needs work » Needs review
FileSize
1.41 KB

I'm attaching a patch that invokes hook_field_presave() as well as hook_field_load(). I'm also reassigning this to D7 as this issue is not applicable to D8 due to the field-API being rewritten in a OOP fashion.

Status: Needs review » Needs work

The last submitted patch, field_hook_presave_and_load_for_default_values.patch, failed testing.

torotil’s picture

Status: Needs work » Needs review
FileSize
1.43 KB

I simply forgot the arguments for entity_extract_ids(). Obviously it was late yesterday :)

Status: Needs review » Needs work

The last submitted patch, field_hook_presave_and_load_for_default_values.patch, failed testing.

torotil’s picture

EDIT: never mind.

torotil’s picture

Status: Needs work » Needs review
FileSize
1.45 KB

Another attempt. (Locally the previously failing tests pass now)

torotil’s picture

Issue summary: View changes

Adding url_field_load() in relevant code.

Dave Reid’s picture

bineetchaubey’s picture

i am always getting empty value of $items in hook_field_presave(). while i am using field api .