Problem/Motivation

See #2807705: FormattableMarkup::placeholderFormat() can result in unsafe replacements. We've supported placeholders that don't start with @, !, % or : forever (! was removed in Drupal 8). We no longer support them.

In Drupal 8 we trigger 2 different errors.

  • If the placeholder starts with a non alphanumeric error then we trigger a E_USER_ERROR.
  • If the placeholder start with an alphanumeric character AND is in contained in the string then we trigger a E_USER_DEPRECATED.

Proposed resolution

  • Trigger a silenced E_USER_DEPRECATION if the placeholder is not in the string and starts with an alphanumeric character. This situation was not accounted for in Drupal 8 so we need to follow our deprecation policy.
  • For all other occurrences of non standard placeholders trigger a E_USER_WARNING on all non standard placeholders passed to \Drupal\Component\Render\FormattableMarkup::placeholderFormat(). E_USER_WARNING is chosen because we do not do the replacement and therefore there are no risks in continuing on. This is slightly less strict than the current behaviour of issuing an E_USER_ERROR but this is a better experience for real sites. The error will be logged but the site will continue to work. This is unlikely to be a critical error.

Remaining tasks

User interface changes

None

API changes

None - non standard placeholders have not been supported since 8.2.0 - #2807705: FormattableMarkup::placeholderFormat() can result in unsafe replacements

Data model changes

None

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

alexpott created an issue. See original summary.

alexpott’s picture

catch’s picture

Title: Remove support for non-standard placeholders in FormattableMarkup::placeholderFormat() » Switch from deprecation notice to exception for non-standard placeholders in FormattableMarkup::placeholderFormat()

Just re-titling, since we don't support these as such already.

catch’s picture

Should probably be an assert (+ no-op) rather than an exception?

katbat’s picture

Assigned: Unassigned » katbat
apaderno’s picture

Assigned: katbat » Unassigned

Version: 9.x-dev » 9.0.x-dev

The 9.0.x branch will open for development soon, and the placeholder 9.x branch should no longer be used. Only issues that require a new major version should be filed against 9.0.x (for example, removing deprecated code or updating dependency major versions). New developments and disruptive changes that are allowed in a minor version should be filed against 8.9.x, and significant new features will be moved to 9.1.x at committer discretion. For more information see the Allowed changes during the Drupal 8 and 9 release cycles and the Drupal 9.0.0 release plan.

alexpott’s picture

Status: Active » Needs review
FileSize
2.84 KB

I think we can be a bit more lenient here are trigger a helpful warning. We've not being doing the replacement since at least 8.3.0 so people have had time to fix their placeholders but there's no reason to error out here - we'll leave the string alone and the warning will be useful.

Status: Needs review » Needs work

The last submitted patch, 8: 2807743-8.patch, failed testing. View results

alexpott’s picture

Status: Needs work » Needs review
FileSize
3.06 KB
4.99 KB

Hmmm I think we might be only able to handle this we a proper deprecation as there is a case we didn't error for.

Status: Needs review » Needs work

The last submitted patch, 10: 2807743-10.patch, failed testing. View results

alexpott’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
707 bytes
5.68 KB
alexpott’s picture

Issue summary: View changes

Status: Needs review » Needs work

The last submitted patch, 12: 2807743-12.patch, failed testing. View results

alexpott’s picture

Status: Needs work » Needs review
FileSize
2.37 KB
8.05 KB

Fixing the remaining errors.

alexpott’s picture

+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -820,7 +820,7 @@ public function getCountLabel($count) {
-    return $this->formatPlural($count, $this->label_count['singular'], $this->label_count['plural'], ['context' => $context]);
+    return $this->formatPlural($count, $this->label_count['singular'], $this->label_count['plural'], [], ['context' => $context]);

This is fun one. The new deprecation is catching an incorrect setting of context!

I have been wondering if we should just silently ignore such placeholders. This convinces me we should check the validity of them as it helps find things like this.

I think we might want to split this fix out into a separate issue so it can be fixed in 8.9.x too.

andypost’s picture

alexpott’s picture

catch’s picture

Title: Switch from deprecation notice to exception for non-standard placeholders in FormattableMarkup::placeholderFormat() » Switch from deprecation notice to warning for non-standard placeholders in FormattableMarkup::placeholderFormat()
+++ b/core/lib/Drupal/Component/Render/FormattableMarkup.php
@@ -231,17 +231,12 @@ protected static function placeholderFormat($string, array $args) {
-            trigger_error('Invalid placeholder (' . $key . ') in string: ' . $string, E_USER_ERROR);
+          // Deprecate suuport for random variables that won't be replaced.
+          if (ctype_alpha($key[0]) && strpos($string, $key) === FALSE) {
+            @trigger_error(sprintf('Support for keys without a placeholder prefix is deprecated in Drupal 9.1.0 and will be removed in Drupal 10.0.0. Invalid placeholder (%s) with string: "%s"', $key, $string), E_USER_DEPRECATED);
           }

I agree with deprecating this for Drupal 10, but reckon we could still deprecate it in 8.9 - it's deprecating a code path, not deprecating actual code, and code doing this is already buggy.

apaderno’s picture

There is a small typo in the following comment (suuport instead of support).

+          // Deprecate suuport for random variables that won't be replaced.
+          if (ctype_alpha($key[0]) && strpos($string, $key) === FALSE) {
+            @trigger_error(sprintf('Support for keys without a placeholder prefix is deprecated in Drupal 9.1.0 and will be removed in Drupal 10.0.0. Invalid placeholder (%s) with string: "%s"', $key, $string), E_USER_DEPRECATED);
andypost’s picture

xjm’s picture

Version: 9.0.x-dev » 9.1.x-dev

This is disruptive, so it would be at least a minor-only change. Since 8.9.x and 9.0.x are now in beta, I'm moving this to 9.1.x. Thanks!

I wonder if there's a way to do this less disruptively with a deprecation for the minor?

alexpott’s picture

Rerolled now the blocker landed and fixed #20.

neclimdul’s picture

Status: Needs review » Reviewed & tested by the community

I wonder if there's a way to do this less disruptively with a deprecation for the minor?

That's sort of what the patch does. The patch can be hard to read in places but summarizing the test changes we can see the actual effect of the change.

|First Character    |Matches in string?  | Test value    | Change               |
|-------------------|--------------------|---------------|----------------------|
|Non-alpha          |Always              |'-placeholder' |ERROR => WARNING      |
|Alpha              |Matches             |'placeholder'  |DEPRECATED => WARNING |
|Alpha              |No matches          |'foo'          |DEPRECATED            |

So this patch is doing 3 things but only 2 should matter.
1) Removing a deprecation. Seems like this should have been in 9.0 but it didn't have a timeline or standard deprecation testing. Probably because of its age.
2) Deprecating a previously unhandled error condition showing mistakes was silently thrown away previously. This is a DX improvement as seen in #16 but maybe isn't allowed in 9.0.

If we really wanted to target 9.0 we could just split the new deprecation and replace it with a TODO or just backport this without the deprecation. A follow up at this point would be one line and a test but I'm not sure it is worth it since we are so late in the 9.0.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 23: 2807743-9.1.x-23.patch, failed testing. View results

alexpott’s picture

Status: Needs work » Reviewed & tested by the community

I think #23 just failed due to a random JS test fail.

catch’s picture

Status: Reviewed & tested by the community » Needs review
  1. +++ b/core/includes/errors.inc
    @@ -149,6 +149,11 @@ function error_displayable($error = NULL) {
     
    +  // Remove 'severity_level' as is not a valid replacement for t().
    +  $severity = $error['severity_level'];
    +  unset($error['severity_level']);
    +  $error['%severity_level'] = $severity;
    +
    

    I feel like I'm missing something, but:

    1. Where is $error['%severity_level'] used?

  2. +++ b/core/includes/errors.inc
    @@ -167,7 +172,7 @@ function _drupal_log_error($error, $fatal = FALSE) {
           // Provide the PHP backtrace to logger implementations.
    -      \Drupal::logger('php')->log($error['severity_level'], '%type: @message in %function (line %line of %file) @backtrace_string.', $error + ['backtrace' => $backtrace]);
    +      \Drupal::logger('php')->log($severity, '%type: @message in %function (line %line of %file) @backtrace_string.', $error + ['backtrace' => $backtrace, 'severity_level' => $severity]);
         }
         catch (\Exception $e) {
    

    2. Aren't we now passing $error['%severity_level'] and $error['severity_level'] to the ->log() call?

neclimdul’s picture

Status: Needs review » Needs work

yeah good catch... catch. I think without looking too close it just made sense to keep it. I dug through this for a while and error is 99% a local variable and its not used locally and when we hand it off we add the variable back so in the bigger picture as shown in 2) setting '%severity_level' doesn't seem needed. If a logger wants to add the log severity to the message for some reason it has all it needs to make that happen.

Looking at the diff in #27.1 in a different context, I don't think we actually pass this to t(), it looks like its all strtr and FormatableMarkup right? We can just say "... as it is not a valid replacement."

alexpott’s picture

Status: Needs work » Needs review
FileSize
7.44 KB
7.44 KB

Yeah I added %severity_level for a sort of BC. But I agree that it is not required.

However I disagree about the \Drupal::logger('php')->log($severity, '%type: @message in %function (line %line of %file) @backtrace_string.', $error + ['backtrace' => $backtrace, 'severity_level' => $severity]); - yes contrib implementations could be like \Drupal\syslog\Logger\SysLog::log() and use the $level provided but they could also use $context['severity_level'] and we should not break that here.

alexpott’s picture

FileSize
2.31 KB

Here's the correct interdiff.

catch’s picture

Status: Needs review » Reviewed & tested by the community

#29 looks better.

  • catch committed e85462d on 9.1.x
    Issue #2807743 by alexpott, catch, kiamlaluno, neclimdul, xjm: Switch...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed e85462d and pushed to 9.1.x. Thanks!

I think it's fine for this to just land in 9.1.x and not be backported.

Status: Fixed » Closed (fixed)

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

TR’s picture

This is still causing problems. Please see #3173103: False positives when identifying what is a placeholder, for deprecation error. In short, the PSR-3 $context parameter holds more than just placeholders, but Drupal generates a warning if the array keys in $context don't conform to the "placeholder" requirements.