1

I'm maintaining a module that I've had a 3rd party dev build for me. I D9 this issue has appeared when using the module that otherwise works in D8 and should be compatible with 8 & 9.

The module creates blocks with event dates pulled from the Songkick service but on D9 pages with a songkick block enabled throws this error:

Drupal\Core\Security\UntrustedCallbackException: Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was _songkick_block_poweredby_prerender. See https://www.drupal.org/node/2966725 in Drupal\Core\Render\Renderer->doTrustedCallback() (line 96 of /var/www/html/web/core/lib/Drupal/Core/Security/DoTrustedCallbackTrait.php).

I'm a mere site builder with limited coding experience so I'm looking for help to fix the issue. The error mentions this page explaining the issue.

The code below from my .module files includes the pre_render in mention.

/**
 * Implements hook_block_view_alter().
 */
function songkick_block_content_view_alter(array &$build) {
    $id = $build['#block_content']->id();
  $block = \Drupal\block_content\Entity\BlockContent::load($id);
  $block_type = $block->type[0]->target_id;
  if ($block_type == 'songkick_block') {
        $build['#pre_render'][] = '_songkick_block_poweredby_prerender';
    }
}

/**
 * Get a Upcoming & past events API data.
 */
function _songkick_block_poweredby_prerender(array &$build) {
  $id = $build['#block_content']->id();
  $block = \Drupal\block_content\Entity\BlockContent::load($id);
  $block_type = $block->type[0]->target_id;
  if ($block_type == 'songkick_block') {
    // Get custom Blocks data.
      $artis_id = $block->field_artist_id[0]->value;
      $upcoming_show = $block->field_display_upcoming_shows[0]->value;
      $past_show = $block->field_display_past_shows[0]->value;
      $event_details = $block->field_ticket_button[0]->value;
      $events_data = [
        'event_details' => $event_details,
        'upcoming_show' => $upcoming_show,
        'past_show' => $past_show
      ];
      $events_data = [
          '#theme' => 'songkick_events',
          '#events_data' => $events_data
      ];

        $client = \Drupal::httpClient();
        $api_key = \Drupal::config('songkick.settings')->get('songkick_key');
        $url = 'http://api.songkick.com/api/3.0/artists/';

        // Upcoming event data.
    if ($upcoming_show == '1') {
            $apikey = $url . $artis_id. "/calendar.json" . "?apikey=". $api_key;
            $request = $client->get($apikey);
            $body = $request->getBody()->getContents();
            $AllData = (array)json_decode($body);
            // Last page data
            $per_page = $AllData['resultsPage']->perPage;
            $total_entry = $AllData['resultsPage']->totalEntries;
            $page = (int)($total_entry / $per_page);
            //$mainData = $AllData['resultsPage']->results->event;
            $temp_var = [];
            for ($x = $page; $x >= 0; $x--){ 
                $final_api_key = $apikey . "&page=" . $x;
                $upcoming_event_request = $client->get($final_api_key);
                $upcoming_event_body = $upcoming_event_request->getBody()->getContents();
                $UpcomingEventAllData = json_decode($upcoming_event_body);
                $mainData = array_reverse($UpcomingEventAllData->resultsPage->results->event);

                //$temp_data[] = $mainData;
                foreach ($mainData as $value) {
                    $temp_var[] = $value;
                }

            }
            $upcoming_events_data = [
          '#theme' => 'songkick_events',
          '#upcoming_event' => $temp_var
        ];
    }
    else{
            $upcoming_events_data = [
          '#theme' => 'songkick_events',
          '#upcoming_event' => ''
        ];  
    }

    // Past event data.
    if ($past_show == '1') {
            $apikey = $url . $artis_id. "/gigography.json" . "?apikey=". $api_key;
            $request = $client->get($apikey);
            $body = $request->getBody()->getContents();
            $AllData = (array)json_decode($body);

            // Last page data
            $per_page = $AllData['resultsPage']->perPage;
            $total_entry = $AllData['resultsPage']->totalEntries;
            $page = (int)($total_entry / $per_page);

            $temp_var = [];
            for ($x = $page; $x >= 0; $x--){ 
                $final_api_key = $apikey . "&page=" . $x;
                $past_event_request = $client->get($final_api_key);
                $past_event_body = $past_event_request->getBody()->getContents();
                $PastEventAllData = json_decode($past_event_body);
                $mainData = array_reverse($PastEventAllData->resultsPage->results->event);

                //$temp_data[] = $mainData;
                foreach ($mainData as $value) {
                    $temp_var[] = $value;
                }
            }
            //$final_api_key = $apikey . "&page=" . $page;
            // $past_event_request = $client->get($final_api_key);
            // $past_event_body = $past_event_request->getBody()->getContents();
            // $PastEventAllData = (array)json_decode($past_event_body);
            // $mainData = array_reverse($PastEventAllData['resultsPage']->results->event);
            $past_events_data = [
          '#theme' => 'songkick_events',
          '#past_event' => $temp_var
        ];
    }
    else{
            $past_events_data = [
          '#theme' => 'songkick_events',
          '#past_event' => ''
        ];
    }

    // Merge Upcoming & past evenst data.
    $event_data = array_merge($upcoming_events_data,$past_events_data,$events_data);

    // Return events data.
        return $event_data;
  }
}

I've tried following the instructions in this comment and have added a file named SongkickBlockPoweredByViewBuilder.php in the src folder with this code:

<?php

namespace Drupal\Songkick;

use Drupal\Core\Security\TrustedCallbackInterface;

/**
 * Provides a trusted callback for the Songkick Poweredby block.
 *
 */
class SongkickBlockPoweredByViewBuilder implements TrustedCallbackInterface {

    /**
     * {@inheritdoc}
     */
    public static function trustedCallbacks() {
     return ['preRender'];    
   }
 
   /**
    * Sets Songkick - #pre_render callback.
    */
   public static function preRender($build) {
     $count = $build['content']['#count'];
     $build['content']['#count_text'] = \Drupal::translation()->formatPlural($count, '(@count)', '(@count)');
     return $build;
   }
 
 }

And I've added the following to my .module file:

use Drupal\Songkick\SongkickBlockPoweredByViewBuilder;

/**
 * Implements TrustedCallbackInterface
 */
function _songkick_block_poweredby_prerender(array &$build, Drupal\Core\Block\BlockPluginInterface $block) {
  $build['#pre_render'][] = [SongkickBlockPoweredByViewBuilder::class, 'preRender'];
}

Then the site throws this fatal error:

Fatal error: Cannot redeclare _songkick_block_poweredby_prerender() (previously declared in /var/www/html/web/modules/contrib/songkick/songkick.module:14) in /var/www/html/web/modules/contrib/songkick/songkick.module on line 51

The fatal error goes away if I remove the declaration in line 14, but the original error then persist. I'm at a loss about how to define the function in line 51 in a way that resolves the issue. Line 51 begins right after this comment in the above excerpt from the .module file:

/*** Get a Upcoming & past events API data. */

Any tips?

7
  • 1
    Drupal is giving you a warning with a specific link that explains the problem. If you don't understand the linked docs-- what is it that you don't understand? And it always helps if you post the actual code that is causing the problem ;) Oct 21 at 14:32
  • Valid points @PatrickKenny, thanks. I've edited the original post to include those things. Oct 21 at 15:35
  • This comment and the reply to it on the issue linked to in the error message contain examples of what you need to do: drupal.org/node/2966725#comment-13948868
    – Lambic
    Oct 21 at 15:53
  • Thanks a lot @Lambic. I just tried my best at that and I must have done something wrong as it results in a fatal error. I've updated my question with the steps I took. Can I persuade you to take a look? Oct 21 at 16:21
  • Looks like you have the same function defined twice in songkick.module, once at line 14 and again at line 51
    – Lambic
    Oct 21 at 17:15
4

You have the right idea, there are just a few details that need to be fixed.

The TrustedCallbackInterface was first added to Drupal 8.8; it didn't exist before Drupal 8.8. While it is optional in Drupal 8.8 and 8.9, it is mandatory in Drupal 9. So your module isn't compatible with Drupal 9 until you make this change.

First, copy the entire body of your original version of _songkick_block_poweredby_prerender() into your public static function preRender($build). That is all that should be in the body of the preRender() method.

Second, delete the function _songkick_block_poweredby_prerender() (both copies) from songkick.module. You no longer need it because you have put that code in Drupal\songkick\SongkickBlockPoweredByViewBuilder.

Now put the use Drupal\songkick\SongkickBlockPoweredByViewBuilder; statement at the top of songkick.module with all the other use statements. Then change this statement:

$build['#pre_render'][] = '_songkick_block_poweredby_prerender';

to point to your new code instead. Like this:

$build['#pre_render'][] = [SongkickBlockPoweredByViewBuilder::class, 'preRender'];

Also note that the correct namespace has a lowercase 's' in 'songkick'. This has to be the same as the module's machine name, and machine names don't allow capital letters. So you need to make sure the namespace declaration in SongkickBlockPoweredByViewBuilder uses the lowercase, and that your use statement in songkick.module uses the lowercase.

Drupal is picky about naming, so also be sure that if your .module file is in songkick/songkick.module, the class SongkickBlockPoweredByViewBuilder is in songkick/src/SongkickBlockPoweredByViewBuilder.php

4
  • Thanks so much. Original error now gone but the block doesn't render the list of events anymore, just the block settings. I've updated the question to reflect the steps taken and the new log reports. Any insights would be massively appreciated. Oct 22 at 9:59
  • You didn't remove these three lines from your preRender(), so you're returning from the function before your event data code is even run: $count = $build['content']['#count']; $build['content']['#count_text'] = \Drupal::translation()->formatPlural($count, '(@count)', '(@count)'); return $build;
    – anonymous
    Oct 23 at 0:03
  • 1
    And at this point, we're debugging your code, which is outside the scope of this issue and not really appropriate for StackExchange.
    – anonymous
    Oct 23 at 0:05
  • Valid point, sorry about that. Anyways, thanks so much for your help. I'll accepting your answer now. Oct 23 at 11:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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