phpdrupal-7drupal-hooksdrupal-fieldsdrupal-field-api

Drupal-7 how to get hook_field_[formatter_]prepare_view() invoked without overwriting existing formatter


From my module, I'm looking for a way to change text-fields value during rendering process, but WITHOUT creating a new formatter, and BEFORE the currently affected formatter works.

In other words I want my changes always made on any text-field, as a generic preparatory step, regardless of which formatter will work afterwards.

For this to work:

Any idea? Thanks in advance.


Solution

  • I actually found a pretty way to do what I looked for.
    The method is quite invasive but works fine and may be re-used for different cases.

    1. To be as clear as possible, first I rephrase my question in terms of a general use case:

    In the rendering process, how to permit a module to change value of one or more fields (given field-id, given field-type...) before the formatter (if any) do its own job?

    2. The problem to accomplish this:

    We can't make the module define a new formatter, because only one may be defined at the same time for the same field

    3. The strategy which led me to the desired result:

    4. Detail about how to graft my module in the process:

    Whith hook_field_formatter_info_alter(&$info) we face the following $info structure:

    $info = array(
      ['formatter machine name'] = array(
        ['label'] => 'Human readable formatter description',
        ['field types'] => array(
          [0] => 'a_field_type,
          [1] => 'another_field_type',
          # ...
        ),
        ['settings'] => array(
          ['option A'] => 'option A value',
          ['option B'] => 'option B value',
          # ...
        ),
        ['module'] => 'formatter_module_name',
      ),
      ['formatter machine name'] = array(
        # ...
      ),
      # ...
    );
    

    We can easily run through the formatters list and look at "field types" index to select which ones are concerned by our needs.
    Then for each involved one, we can:

    1. substitute our own module name to formatter module name in "module" index
    2. add a new sub-index in "settings" index (say "our module graft") to register the original formatter module name

    So our hook_field_formatter_info_alter() will be something like:

    function mymodule_field_formatter_info_alter(&$info) {
      if($info) {
        foreach($info as $name=>$formatter) {
          if(
            !@$formatter['settings']['mymodule graft'] # or already grafted
          and
            array_intersect($formatter['field types'],
              array('text','text_long','text_with_summary')) # here it is for text fields only
          ) {
            # substitute mymodule to original module:
            $info[$name]['settings']['mymodule graft']=$formatter['module'];
            $info[$name]['module']='mymodule';
          }
        }
      }
    }
    

    Once flushing class registry, now all involved fields have their formatting phase redirected to our own module.
    NOTE: installing a new formatter now requires flushing class registry again, in order our module to take it in hand also.

    5. Detail about how to make original formatters to work after us:

    As stated above, now it is our own module which is notified when a field has to been formatted, rather than the originally affected formatter.
    So we must react in our hook_field_formatter_prepare_view(), which should look like:

    function mymodule_field_formatter_prepare_view(
      $entity_type,$entities,$field,$instances,$langcode,&$items,$displays
    ) {
      # here we do our own job with field values:
      if($items) {
        foreach($items as $nid=>$node_data) {
          # ...
        }
      }
      # then we give original formatter a chance to execute its own hook:
      foreach($displays as $display) {
        $hook=
          $display['settings']['mymodule graft'].'_field_formatter_prepare_view';
        if(function_exists($hook)) {
          $hook(
            $entity_type,$entities,$field,$instances,$langcode,$items,$displays
          );
        }
      }
    }
    

    Finally we also must give a chance to other formatters hooks to execute.
    For each one, it should look like (replace HOOK and ARGS by the right data for each hook):

    function mymodule_field_formatter_HOOK(ARGS) {
      $hook=$display['settings']['mymodule graft'].'_field_formatter_HOOK';
      if(function_exists($hook)) {
        return $hook(ARGS);
      }
    }
    

    Hope this helps...