1

This is one of those, "Looked everywhere in the API for a hook, can't find it" questions. There must be a hook like I'm looking for, but none of my searches have found anything.

I'm looking for a simple hook in D7 that lets me edit text filtered content, after it's gone through a text input filter, before it's saved: regardless of which input filter, and regardless of entity type (i.e. not just nodes).

I can't find a suitable hook. The new-style hook_filter() alternative hook_filter_info() isn't suitable because I want this code to run on all input filters, so I don't want to define a new one.

hook_node_save() is too specific. I could maybe hook on hook_entity_presave() then load field info, then look through all fields looking for fields that are longtext fields, then act on those, but I'm hoping there's something more direct. That sounds like a lot of extra work for something simple. I'd prefer not to have every single entity save load all the field info and check every field (unless there's a very low-overheads way to do that).

What's the simplest, cleanest way to get code to run on all text that has gone through an input filter, regardless of the input filter?

4
  • hook_field_presave?
    – mpdonadio
    Commented Dec 5, 2012 at 1:39
  • @MPD reading the comments on the API, "Also note that this hook is only called on the module that defines the field. So you can't use it to hook into presave functionality of other modules' fields. AFAICT, the best hook to use for that is hook_field_attach_presave(), though it a bit tough since the only parameters are $entity_type and $entity." - which means its prob worse than hook_entity_presave() since we'd have to load and loop through field info on every field looking for fields with right type, not just every entity Commented Dec 5, 2012 at 1:55
  • Hmmm. I vaguely recall doing something similar recently. After I re-read the API page, I think I may have actually used the entity presave, as your answer shows. I'll check when I am back in the office tomorrow.
    – mpdonadio
    Commented Dec 5, 2012 at 2:07
  • 1
    As a side note, input filters are not invoked when an entity is saved, but when an entity is shown. If you want to alter a field before it gets saved, hook_filter_info() is not the way to go.
    – avpaderno
    Commented Dec 5, 2012 at 3:03

2 Answers 2

3

Here's my best clunky workaround so far:

  <?php
  function mymodule_entity_presave($entity, $type) {
  $fields = field_info_fields();
  foreach($fields as $field) {
    if ($field['type'] == "text_long" || $field['type'] == "text_with_summary") {
      if( isset($entity->{$field['field_name']}) ){
        foreach ($entity->{$field['field_name']} as $langcode => $items) {
          foreach ($items as $delta => $item) {
            $value =&$entity->{$field['field_name']}[$langcode][$delta]['value']; 
            $value = _mymodule_do_stuff( $value );

            if ($field['type'] == "text_with_summary") {
              $summary =&$entity->{$field['field_name']}[$langcode][$delta]['summary'];
              $summary = _mymodule_do_stuff( $summary );

            }
          }
        }
      }
    }
  }
}

Not ideal at all, but I think it beats the only non-core-hacking alternative I can see which is to use hook_field_attach_presave();. The code would be almost identical, and I believe it would be worse since we'd have to call and loop through field_info_fields() once for every field on every saved entity (instead of once for every saved entity).

2
  • If you are interested to know if the field is going through text processing (a.k.a. input filters), you can check the "text_processing" key of the instance settings.
    – avpaderno
    Commented Dec 5, 2012 at 3:06
  • Apparently, there isn't any difference between hook_entity_presave(), and hook_field_attach_presave(). The code you would use for a hook should be used for the other hook too.
    – avpaderno
    Commented Dec 5, 2012 at 5:33
1

Other than clunky workarounds like the one you've suggested, I can't see a way to do it without patching the core filter module. It's the check_markup() function which is ultimately responsible for running the filters against the text, round about line 757 of filter.module:

// Perform filtering.
foreach ($filters as $name => $filter) {
  if ($filter->status && isset($filter_info[$name]['process callback']) && function_exists($filter_info[$name]['process callback'])) {
    $function = $filter_info[$name]['process callback'];
    $text = $function($text, $filter, $format, $langcode, $cache, $cache_id);
  }
}

The only thing the function does after that is cache the text if necessary and return, so between that and no hooks being invoked inside the loop I think you're out of luck.

If you go down the route of patching the module (with extreme caution and a backup plan of course) it wouldn't take much, just few lines like

foreach (module_implements('filter_text_process') as $module_name) {
  $function = $module_name . '_filter_text_process';
  $text = $function($text, $name, $filter);
}

in the filter loop should do it. It's basically just adding a new hook that your module can implement like so:

function MYMODULE_filter_text_process($text, $filter_name, $filter) {
  return do_something_to($text);
}

Patching core is obviously never desirable, but sometimes unavoidable when the alternative is taking a performance hit like the one you've described and the original code wasn't designed for the purpose you need.

3
  • +1 and thanks for the (important) bad news... I'm keen to avoid core hacking for obvious reasons, so I've added a clunky workaround as an answer, would you be able to comment on roughly speaking how much of a performance hit we're talking about? I think it'd all hinge on how significant calling field_info_fields(); is, whether it's just taking existing data from memory or causing yet another query. Commented Dec 5, 2012 at 2:07
  • Since it calls _field_info_collate_fields() with no params, which tries to get info from existing variable, looks like it shouldn't be too bad... but I'm no expert here. Commented Dec 5, 2012 at 2:14
  • Input filters are invoked when an entity is viewed, not when it is saved. When an entity is saved (e.g. a comment, a node) Drupal is not invoking its input filters.
    – avpaderno
    Commented Dec 5, 2012 at 3:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.