Problem/Motivation

On Drupal 11, Smart Trim triggers a fatal error when tokens are evaluated:

//Call to undefined function _token_field_label() line 34
$labels = _token_field_label($entity_type_id, $field_name);

The error originates from:

modules/contrib/smart_trim/smart_trim.tokens.inc line 34

Specifically in hook_token_info_alter(), where Smart Trim calls the internal Token helper function _token_field_label().

This function no longer exists in Drupal 11, causing a hard failure for anonymous users and during cache rebuilds.

Steps to reproduce

  1. Install Drupal 11.3.x
  2. Enable the Token module
  3. Enable Smart Trim 2.2.0
  4. Rebuild caches or load a page that evaluates tokens (often anonymous pages)

Runtime proof

drush php:eval "var_dump(function_exists('_token_field_label'));"
Output on Drupal 11:

bool(false)

Proposed resolution

  foreach ($fields as $field_name => $field) {
      assert($field instanceof FieldStorageDefinitionInterface);
      //$labels = _token_field_label($entity_type_id, $field_name);
      //$label = array_shift($labels);
		
   // Drupal 11–safe way to get the label.
      $label = $field->getLabel() ?: $field_name;

      if ($field->getType() === 'text_with_summary') {
        $info['tokens'][$token_type][$field_name . '-smart-trim'] = [
          'name' => t('@label (Smart trim summary)', ['@label' => $label]),
          'description' => t('Smart trimmed version of the field or the summary.'),
        ];
      }
    }
CommentFileSizeAuthor
#12 3566575-12.patch1.76 KBjonasanne

Issue fork smart_trim-3566575

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

harpade created an issue. See original summary.

harpade’s picture

Issue summary: View changes
aelfendir’s picture

I just encountered this same issue with Drupal 10.5.8 and Smart Trim 2.2.0 when updating modules.

Apparently related to recent changes in Token module (https://www.drupal.org/project/token/releases/8.x-1.17). Token updated from 1.16.0 to 1.17.0 in my case. When I downgrade Token back to 1.16.0, the error goes away.

jurgenhaas’s picture

Just ran into this as well, and yes, the function _token_field_label no longer exists in the token module since they implemented OO hooks in the latest release. Functions with a leading underscore like _token_field_label are private and shouldn't ever be used as it is expected that they either change or disappear without notice.

This one is now \Drupal\custom_field\Hook\TokenHooks::tokenFieldLabel, but with a private scope, so it can't be used even with an update code here in smart_trim. To solve this, the method needs to be implemented by smart_trim itself.

jurgenhaas’s picture

Version: 2.2.0 » 2.x-dev
Priority: Normal » Critical
Status: Active » Needs review
Issue tags: -Drupal 11.3 compatibility
mrshowerman’s picture

Version: 2.x-dev » 2.2.0
Priority: Critical » Normal
Issue summary: View changes
Status: Needs review » Active

I have the same issue. Was working on a solution when I found out that @jurgenhaas had already fixed it.
Left a small nit on the MR, otherwise working fine.

Agree that this is critical. Thanks!

mrshowerman’s picture

Version: 2.2.0 » 2.x-dev
Priority: Normal » Critical
Status: Active » Needs review
lostcarpark’s picture

Hmmm, this is probably the quickest to fix the problem, but I don't love it as a long term solution.

We are replacing a global public internal function (that we probably shouldn't have been calling in the first place), with our own global public internal function.

I think a cleaner solution would be to create a service to provide our version of the function. This would mean that the function wouldn't need to be loaded into memory until needed (afaik, global functions have to be always loaded). It would also allow the functions dependencies to be injected. Unfortunately, I think we'd have to use \Deupal::service() to call our service function from the tokens.inc file.

As an aside, why are we still using tokens.inc files in modules, rather than a plugin?

jurgenhaas’s picture

@lostcarpark couldn't agree more. As you said, this needs a quick resolution and hence, this approach is probably the right way for a patch release maybe as soon as today?

The proper solution would be to implement OO hooks in smart_trim as well. That would then also bring tokens into a hook class, you don't even need plugins for that.

kieran.cott’s picture

Patch from mr !119 works for me, grateful for the speedy response on this!

jonasanne’s picture

StatusFileSize
new1.76 KB

Uploading the diff of the MR to use it as a patch.

I can confirm this patch works!
Thanks for the quick fix.

ckhalilo made their first commit to this issue’s fork.

ckhalilo’s picture

Hi,

I have made some changes in My PR, with Dependency Injection.

This fix for both D10 and D11

Best Regards
Khalil

ben25’s picture

thanks for the quick work on this guys! lots of sites are breaking due to this lol

lostcarpark’s picture

Tests were failing. The issue seems to have been with field storage creation for Body field. I've added creation if not found to fix. Passing in !119. Will cherry-pick into !120.

lostcarpark’s picture

!120 tests are now passing for all except previous major. This seems to be because there isn't an entry for SmartTrimHooks in services.yml.

joegl’s picture

Patch in #12 applies successfully to 2.2.0. The latest changes to MR 119 do not apply cleanly as a patch. I did not test MR 120.

lostcarpark’s picture

@jurgenhaas, I will be talking with @ultimike later today. If we could have this issue RTBC, I'm sure it would help him to get a release with the fix as quickly as possible.

I'm wondering is there a test case we can add to explicitly cover off this bug, or is it enough that the existing tests fail without it?

lostcarpark’s picture

@ckhalilo I do like this approach. Will discuss which change to go with with @ultimike. I wonder did you omit to push the updated services.yml, as the SmartTrimHooks class isn't defined as a service, which prevents the .module file hooks from calling it.

ckhalilo’s picture

Hi @lostcarpark

The service already updated : https://git.drupalcode.org/project/smart_trim/-/blob/f54812b37c6c8ad21f1...

Take your time.

Best Regards.
Khalil

ultimike made their first commit to this issue’s fork.

digitaltodd’s picture

I'm seeing this too - just reported it in token, since I just did an update
https://www.drupal.org/project/token/issues/3566726

on drupal 10.6.1

ultimike’s picture

I've reviewed this during DrupalEasy Office Hours with @lostcarpark and others - thanks so much to @harpade, @jurgenhass, @lostcarpark, @mrshowerman, @jonasanne, and @ckhalilo.

I added one small comment to clarify the change in the test.

Before tagging a new release, I'm going to leave this at "Needs review" - will someone please confirm that this all works in older version(s) of Token (8.x-1.16 or slightly earlier.)

thanks,
-mike

  • ultimike committed 7ae36c78 on 2.x authored by ckhalilo
    Refactor `smart_trim` token logic into a dedicated service for improved...
johnatas’s picture

Hey,

I can confirm the patch #12 is OK with :

  • Drupal 11.3.2
  • Smart Trim 2.2.0
  • Token 1.17.0

Thanks for the work.

helmo’s picture

Thanks,

I can also confirm that the patch from #12 works on core 10.6.2 and token 1.17.0

I first noticed the problem when visiting /admin/reports/status

ultimike’s picture

I DM'd with @markie earlier today and he suggested that we update the composer.json to require Token 8.1-1.17

I'm away from my laptop for the next few days - can someone else do this, please?

thanks,
-mike

lostcarpark’s picture

I've opened MR !121 which updates the minimum version of Token to 8.x-1.17.

I suggest a new minor release. It's been over a year since 2.2.0 was released, and there are several unreleased features in the 2.x branch, including the token support, so a 2.3.0 release would make sense.

johnatas’s picture

Hi,

Version 2.3.1 fixes the issue. Can we consider this issue as resolved?

aelfendir’s picture

Thanks for the swift action on this issue, everyone!
I tested Smart Trim 2.3.1 with Token 1.17.0 (on Drupal 10.5.8) and everything seems to work fine now.

mark_fullmer’s picture

Since Token 1.17.0 declares its core compatibility at ^10.3, if Smart Trim is going to declare its minimum compatible version of Token at 1.17.0, then Smart Trim's minimum core compatibility becomes ^10.3. Right now, it's set to ^10.1.

eduardo morales alberti’s picture

We got the following error on our site on the site install after upgrading from the 2.2.0 to 2.3.1 from Drupal 11.2.10:
We need more info, but here is the backtrace:

TypeError: Cannot access offset of type Drupal\Core\StringTranslation\TranslatableMarkup in isset or empty in /var/www/html/docroot/core/lib/Drupal/Core/Entity/EntityFieldManager.php on line 722 #0 /var/www/html/docroot/modules/contrib/smart_trim/src/SmartTrimTokens.php(144): Drupal\Core\Entity\EntityFieldManager->getFieldLabels()

#1 /var/www/html/docroot/modules/contrib/smart_trim/src/SmartTrimTokens.php(59): Drupal\smart_trim\SmartTrimTokens->getFieldLabels()

#2 /var/www/html/docroot/modules/contrib/smart_trim/smart_trim.tokens.inc(16): Drupal\smart_trim\SmartTrimTokens->tokenInfoAlter()

#3 /var/www/html/docroot/core/lib/Drupal/Core/Extension/ModuleHandler.php(474): smart_trim_token_info_alter()

#4 /var/www/html/docroot/modules/contrib/token/src/Token.php(41): Drupal\Core\Extension\ModuleHandler->alter()

#5 /var/www/html/docroot/modules/contrib/token/src/Token.php(100): Drupal\token\Token->getInfo()

#6 /var/www/html/docroot/modules/contrib/token/src/TokenModuleProvider.php(57): Drupal\token\Token->getTokenInfo()

#7 /var/www/html/docroot/core/lib/Drupal/Core/Cache/CacheCollector.php(149): Drupal\token\TokenModuleProvider->resolveCacheMiss()

#8 /var/www/html/docroot/modules/contrib/token/src/TokenModuleProvider.php(49): Drupal\Core\Cache\CacheCollector->get()

#9 /var/www/html/docroot/modules/contrib/token/src/Hook/TokenTokensHooks.php(1074): Drupal\token\TokenModuleProvider->getTokenModule()

#10 /var/www/html/docroot/modules/contrib/token/src/Hook/TokenTokensHooks.php(82): Drupal\token\Hook\TokenTokensHooks->fieldTokens()

#11 [internal function]: Drupal\token\Hook\TokenTokensHooks->tokens()

#12 /var/www/html/docroot/core/lib/Drupal/Core/Extension/ModuleHandler.php(404): call_user_func_array()

#13 /var/www/html/docroot/core/lib/Drupal/Core/Extension/ModuleHandler.php(357): Drupal\Core\Extension\ModuleHandler->Drupal\Core\Extension\{closure}()

#14 /var/www/html/docroot/core/lib/Drupal/Core/Extension/ModuleHandler.php(403): Drupal\Core\Extension\ModuleHandler->invokeAllWith()

#15 /var/www/html/docroot/core/lib/Drupal/Core/Utility/Token.php(359): Drupal\Core\Extension\ModuleHandler->invokeAll()

#16 /var/www/html/docroot/modules/contrib/token/src/Hook/TokenTokensHooks.php(691): Drupal\Core\Utility\Token->generate()

#17 [internal function]: Drupal\token\Hook\TokenTokensHooks->tokens()

#18 /var/www/html/docroot/core/lib/Drupal/Core/Extension/ModuleHandler.php(404): call_user_func_array()

#19 /var/www/html/docroot/core/lib/Drupal/Core/Extension/ModuleHandler.php(357): Drupal\Core\Extension\ModuleHandler->Drupal\Core\Extension\{closure}()

#20 /var/www/html/docroot/core/lib/Drupal/Core/Extension/ModuleHandler.php(403): Drupal\Core\Extension\ModuleHandler->invokeAllWith()

#21 /var/www/html/docroot/core/lib/Drupal/Core/Utility/Token.php(359): Drupal\Core\Extension\ModuleHandler->invokeAll()

#22 /var/www/html/docroot/core/lib/Drupal/Core/Utility/Token.php(241): Drupal\Core\Utility\Token->generate()

#23 /var/www/html/docroot/core/lib/Drupal/Core/Utility/Token.php(191): Drupal\Core\Utility\Token->doReplace()

#24 /var/www/html/docroot/modules/contrib/realname/realname.module(218): Drupal\Core\Utility\Token->replace()

#25 /var/www/html/docroot/modules/contrib/realname/realname.module(187): realname_update()

#26 /var/www/html/docroot/modules/contrib/realname/realname.module(93): realname_load_multiple()

#27 /var/www/html/docroot/core/lib/Drupal/Core/Entity/EntityStorageBase.php(401): realname_user_load()

#28 /var/www/html/docroot/core/lib/Drupal/Core/Extension/ModuleHandler.php(357): Drupal\Core\Entity\EntityStorageBase->Drupal\Core\Entity\{closure}()

#29 /var/www/html/docroot/core/lib/Drupal/Core/Entity/EntityStorageBase.php(400): Drupal\Core\Extension\ModuleHandler->invokeAllWith()

#30 /var/www/html/docroot/core/lib/Drupal/Core/Entity/EntityStorageBase.php(320): Drupal\Core\Entity\EntityStorageBase->postLoad()

#31 /var/www/html/docroot/core/lib/Drupal/Core/Entity/EntityStorageBase.php(263): Drupal\Core\Entity\EntityStorageBase->loadMultiple()

#32 /var/www/html/docroot/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php(277): Drupal\Core\Entity\EntityStorageBase->load()

#33 [internal function]: Drupal\Core\Installer\Form\SiteConfigureForm->submitForm()

#34 /var/www/html/docroot/core/lib/Drupal/Core/Form/FormSubmitter.php(105): call_user_func_array()

#35 /var/www/html/docroot/core/lib/Drupal/Core/Form/FormSubmitter.php(43): Drupal\Core\Form\FormSubmitter->executeSubmitHandlers()

#36 /var/www/html/docroot/core/lib/Drupal/Core/Form/FormBuilder.php(589): Drupal\Core\Form\FormSubmitter->doSubmitForm()

#37 /var/www/html/docroot/core/lib/Drupal/Core/Form/FormBuilder.php(495): Drupal\Core\Form\FormBuilder->processForm()

#38 /var/www/html/docroot/core/includes/install.core.inc(977): Drupal\Core\Form\FormBuilder->submitForm()

#39 /var/www/html/docroot/core/includes/install.core.inc(611): install_get_form()

#40 /var/www/html/docroot/core/includes/install.core.inc(564): install_run_task()

#41 /var/www/html/docroot/core/includes/install.core.inc(122): install_run_tasks()

#42 /var/www/html/vendor/drush/drush/includes/drush.inc(69): install_drupal()

#43 /var/www/html/vendor/drush/drush/includes/drush.inc(53): drush_call_user_func_array()

#44 /var/www/html/vendor/drush/drush/src/Commands/core/SiteInstallCommands.php(174): drush_op()

#45 [internal function]: Drush\Commands\core\SiteInstallCommands->install()

#46 /var/www/html/vendor/consolidation/annotated-command/src/CommandProcessor.php(276): call_user_func_array()

#47 /var/www/html/vendor/consolidation/annotated-command/src/CommandProcessor.php(212): Consolidation\AnnotatedCommand\CommandProcessor->runCommandCallback()

#48 /var/www/html/vendor/consolidation/annotated-command/src/CommandProcessor.php(175): Consolidation\AnnotatedCommand\CommandProcessor->validateRunAndAlter()

#49 /var/www/html/vendor/consolidation/annotated-command/src/AnnotatedCommand.php(389): Consolidation\AnnotatedCommand\CommandProcessor->process()

#50 /var/www/html/vendor/symfony/console/Command/Command.php(318): Consolidation\AnnotatedCommand\AnnotatedCommand->execute()

#51 /var/www/html/vendor/symfony/console/Application.php(1091): Symfony\Component\Console\Command\Command->run()

#52 /var/www/html/vendor/drush/drush/src/Application.php(201): Symfony\Component\Console\Application->doRunCommand()

#53 /var/www/html/vendor/symfony/console/Application.php(356): Drush\Application->doRunCommand()

#54 /var/www/html/vendor/symfony/console/Application.php(195): Symfony\Component\Console\Application->doRun()

#55 /var/www/html/vendor/drush/drush/src/Runtime/Runtime.php(113): Symfony\Component\Console\Application->run()

#56 /var/www/html/vendor/drush/drush/src/Runtime/Runtime.php(40): Drush\Runtime\Runtime->doRun()

#57 /var/www/html/vendor/drush/drush/drush.php(140): Drush\Runtime\Runtime->run()

#58 /var/www/html/vendor/bin/drush.php(119): include('...')

#59 {main}

TypeError: Cannot access offset of type Drupal\Core\StringTranslation\TranslatableMarkup in isset or empty in Drupal\Core\Entity\EntityFieldManager->getFieldLabels() (line 722 of /var/www/html/docroot/core/lib/Drupal/Core/Entity/EntityFieldManager.php).

 [warning] Drush command terminated abnormally.
eduardo morales alberti’s picture

It is quite similar to the issue https://www.drupal.org/project/drupal/issues/3495587 and could be related to the plugin definition. We are not sure, but it is possible that field plugin properties do not support translation. At least we saw that on other modules https://git.drupalcode.org/project/butils/-/commit/b9a19526f68975f389c11...
And in this case, the error seems to come from the translated field label.

https://git.drupalcode.org/project/smart_trim/-/blob/2.x/src/Plugin/Fiel...

/**
 * Plugin implementation of the 'smart_trim' formatter.
 *
 * @FieldFormatter(
 *   id = "smart_trim",
 *   label = @Translation("Smart trimmed"),
 *   field_types = {
 *     "text",
 *     "text_long",
 *     "text_with_summary",
 *     "string",
 *     "string_long"
 *   }
 * )
 */

eduardo morales alberti’s picture

The smart_trim 2.3.1 version introduced a new SmartTrimTokens class that calls $this->entityFieldManager->getFieldLabels().

\Drupal\Core\Entity\EntityFieldManager::getFieldLabels

  $label = $field->getLabel();                                                                                                                                                                                                                                    
  $label_counter[$label] = isset($label_counter[$label]) ? ++$label_counter[$label] : 1;                                                                                                                                                                          
  $all_labels[$label] = TRUE;        

$field->getLabel() returns a TranslatableMarkup object, not a plain string. Then on line 722, the code tries to use this object as an array key:

isset($label_counter[$label]) // $label is a TranslatableMarkup object

In PHP 8.x, you cannot use an object as an array key unless it's explicitly convertible. The isset() call attempts to access $label_counter using a TranslatableMarkup object as the offset, which throws:

TypeError: Cannot access offset of type Drupal\Core\StringTranslation\TranslatableMarkup in isset or empty

eduardo morales alberti’s picture

It is possible that it is related to our serializer (igbinary)
On comment https://www.drupal.org/project/drupal/issues/3495587#comment-16234991

When Drupal caches field definitions (which include labels), the serializer affects how TranslatableMarkup objects are stored and retrieved:

- With standard PHP serializer: Sometimes cached TranslatableMarkup objects might get converted to strings during serialization/unserialization cycles
- With igbinary: Object types are preserved more strictly, so TranslatableMarkup objects remain as objects when retrieved from cache

But there is a Drupal core bug at line 722 - the code should always cast $label to string before using it as an array key, regardless of serializer:

$label = (string) $field->getLabel();

So the bug is in Drupal core, and not smart_trim.

jds1’s picture

Status: Needs review » Reviewed & tested by the community

I had this issue on 10.6.2 and Smart Trim 2.2.0. Upgrading to 2.3.1 fixed it. Based on comments #33 and #34 I'm marking this RTBC. Not sure if the other comments later in this issue warrant moving this back to "Needs Work" or creating a follow-up, but I leave that up to the community =)

joegl’s picture

This should be probably be closed since the specific issue was addressed and merged in a recent release. Eduardo may want to open a new issue.

ultimike’s picture

Status: Reviewed & tested by the community » Fixed

Marking this as fixed.

I moved updating the Drupal core version requirement to #3569291: Update Drupal core requirement to ^10.3

-mike

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

eduardo morales alberti’s picture

Created issue to solve the compatibility with D11.3 https://www.drupal.org/project/smart_trim/issues/3570967

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

mstrelan’s picture

Since 2.2.0 claims it is compatible with ^11 this should be backported to a 2.2.x branch.