Pretty-printing JSON as preformatted text in translatable string passed as argument to method?

drupal.stackexchange https://drupal.stackexchange.com/questions/296197

  •  27-02-2021
  •  | 
  •  

Question

LENGTHY PREAMBLE:

In a Service class, I'd like to display onscreen debug info for users with a specific permission.

In my_custom_module.permissions.yml:

'view debug info':
  title: 'View debug info'
  description: 'Allow user to view DataPartner API debugging messages.'
  restrict access: true

In src/Api/DataPartner.php:

<?php

namespace Drupal\my_custom_module\Api;

/**
 * @file
 * Contains \Drupal\my_custom_module\Api\DataPartner.
 */

use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;

/**
 * Provides API integration methods.
 */
class DataPartner {

  use StringTranslationTrait;

  /**
   * Private function to determine whether displaying debug info is permitted.
   *
   * @return bool
   *   TRUE if debugging is permitted for current user in current environment.
   */
  private function hasDebugPermission() {
    return MY_CUSTOM_MODULE_DEBUG_FLAG && $this->account->hasPermission('view debug info');
  }

There's a bit more to it.

I have left out the dependency injection stuff in *.services.yml and in the Service class, which I'm using to provide methods to deal with checking user account permission, writing Drupal messages to the screen, and other stuff. (I included just enough to show what services I'm injecting, and to show that I'm using the StringTranslationTrait in the Service class rather than injecting that dependency, in case that is relevant to my question.)

I have also defined the constant MY_CUSTOM_MODULE_DEBUG_FLAG to disable the debugging in the production environment and toggle it based on a checkbox in a ConfigForm, but that won't have any effect on my actual question.

Anyway, now I can display the API debugging messages like this:

if ($this->hasDebugPermission()) {
  $this->messenger->addMessage($this->t('Step 2: $response = <br><pre>@value</pre>', [
    '@value' => print_r(json_encode($this->response_decode, JSON_PRETTY_PRINT), TRUE),
  ]));
}

This is fine, as far as it goes, but I wanted better encapsulation. I tried this:

  /**
   * Private function to display debug info if permitted.
   *
   * @param string $message
   *   Translatable message string to be displayed, if permitted.
   * @param array $variables
   *   Values for substitution in translatable string.
   */
  private function displayDebugInfo(string $message, array $variables = []) {
    if ($this->account->hasPermission('view debug info')) {
      $this->messenger->addMessage($this->t($message, $variables));
    }
  }

However, this violates Drupal coding standards because:

WARNING | Only string literals should be passed to t() where possible

There's an interesting discussion of this here.

The linked issue queue mentions that you can mark the string for translation before passing it as an argument to your method, so I tried that:

  /**
   * Private function to display debug info if permitted.
   *
   * @param string $message
   *   Translatable message string to be displayed, if permitted.
   */
  private function displayDebugInfo(string $message, array $variables = []) {
    if ($this->account->hasPermission('view debug info')) {
      $this->messenger->addMessage($message));
    }
  }

If I do that, I need to display the API debugging messages like this:

this->displayDebugInfo($this->t('Step 2: $response = <br><pre>@value</pre>', [
  '@value' => print_r(json_encode($this->response_decode, JSON_PRETTY_PRINT), TRUE),
]));

This lengthy preamble brings me to my question.

ACTUAL QUESTION:

Passing the translatable string to the method in this way causes the HTML markup to be printed to the screen, so that instead of seeing pretty-printed JSON as preformatted text, the user sees a brick of ugly text and markup.

When I examine the markup in the DOM inspector of the browser's Dev Tools, it looks like regular markup, not escaped HTML entities like &lt;pre&gt;.

  • Why is the markup not being interpreted by the browser? (I'm assuming there is a security reason.)
  • If I want to have my cake (preformatted pretty-printed JSON in $this->messenger->addMessage()) and eat it too (with better object-oriented encapsulation), is there any way for me to get what I want?
Was it helpful?

Solution

The simplest method is to use the Markup class directly. This should please coder/phpcs. Core also does this.

<?php

namespace Drupal\my_custom_module\Api;

use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\StringTranslation\StringTranslationTrait;

/**
 * Provides API integration methods.
 */
class DataPartner {

  use StringTranslationTrait;

  /**
   * Private function to determine whether displaying debug info is permitted.
   *
   * @return bool
   *   TRUE if debugging is permitted for current user in current environment.
   */
  private function hasDebugPermission() {
    return MY_CUSTOM_MODULE_DEBUG_FLAG && $this->account->hasPermission('view debug info');
  }

  /**
   * Private function to display debug info if permitted.
   *
   * @param string $message
   *   Translatable message string to be displayed, if permitted.
   */
  private function displayDebugInfo(string $message, array $variables = []) {
    if ($this->account->hasPermission('view debug info')) {
      $this->messenger->addMessage(Markup::create($message)));
    }
  }

}

@file isn't required for namespaced classes.

Licensed under: CC-BY-SA with attribution
Not affiliated with drupal.stackexchange
scroll top