Problem/Motivation

Certain configurations of the Typogrify filter lead to errors.

  1. Enable the Typogrify filter for a text format.
  2. Set either "Thin space in abbreviations" or "Digit grouping in numbers" to something other than the default "Do nothing".
  3. Save the text format.
  4. Clear caches.
  5. Load any page that uses the text format.

You will get a WSOD and the Drupal log will report something like

Error: Access to undeclared static property: Drupal\typogrify\SmartyPants::$method in Drupal\typogrify\SmartyPants::smartNumbers() (line 684 of /app/modules/contrib/typogrify/src/SmartyPants.php)

Proposed resolution

Replace self::$method with a valid callback inside preg_replace_callback().

Fix related errors.

For now, the work-around is to leave those two settings at their default values.

Remaining tasks

Fix the error in the regular expression.

User interface changes

None

API changes

None

Data model changes

None

Release notes snippet

None

Original report by @migmedia

I stumbled upon 2 errors in SmartyPants.

PHP message: Error: Access to undeclared static property: Drupal\typogrify\SmartyPants::$replace_method in /var/www/drupal8/modules/contrib/typogrify/src/SmartyPants.php on line 834 #0 /var/www/drupal8/modules/contrib/typogrify/src/Plugin/Filter/TypogrifyFilter.php(381): Drupal\typogrify\SmartyPants::smartAbbreviation('<p>Bei Fragen o...', '3')
#1 /var/www/drupal8/core/modules/filter/src/Element/ProcessedText.php(118): Drupal\typogrify\Plugin\Filter\TypogrifyFilter->process('<p>Bei Fragen o...', 'de')
#2 [internal function]: Drupal\filter\Element\ProcessedText::preRenderText(Array)
#3 /var/www/drupal8/core/lib/Drupal/Core/Security/DoTrustedCallbackTrait.php(100): call_user_func_array(Array, Array)
#4 /var/www/drupal8/core/lib/Drupal/Core/Render/Renderer.php(781): Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_ren...', 'silenced_deprec...', 'Drupal\\Core\\Ren...')
#5 /var/www/drupal8/core/lib/Drupal/Core/Render/Renderer.php(372): Drupal\Core\Render\Renderer->doCallback('#pre_rend" while reading response header from up
stream, client: 192.168.80.62, server: ...
PHP message: Error: Access to undeclared static property: Drupal\typogrify\SmartyPants::$method in /var/www/drupal8/modules/contrib/typogrify/src/SmartyPants.php on line 684 #0 /var/www/drupal8/modules/contrib/typogrify/src/Plugin/Filter/TypogrifyFilter.php(386): Drupal\typogrify\SmartyPants::smartNumbers('<p>Bei Fragen o...', '3')
#1 /var/www/drupal8/core/modules/filter/src/Element/ProcessedText.php(118): Drupal\typogrify\Plugin\Filter\TypogrifyFilter->process('<p>Bei Fragen o...', 'de')
#2 [internal function]: Drupal\filter\Element\ProcessedText::preRenderText(Array)
#3 /var/www/drupal8/core/lib/Drupal/Core/Security/DoTrustedCallbackTrait.php(100): call_user_func_array(Array, Array)
#4 /var/www/drupal8/core/lib/Drupal/Core/Render/Renderer.php(781): Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_ren...', 'silenced_deprec...', 'Drupal\\Core\\Ren...')
#5 /var/www/drupal8/core/lib/Drupal/Core/Render/Renderer.php(372): Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, A" while reading response header from upstream, client: 192.168.80.62, server: ...

It's an invalid use of self::$variable to reference a local variable, not a class-member.

This is fixed by:

diff --git a/src/SmartyPants.php b/src/SmartyPants.php
index 2660351..a8b82c5 100644
--- a/src/SmartyPants.php
+++ b/src/SmartyPants.php
@@ -681,7 +681,7 @@ class SmartyPants {
           $number_finder = '@(?:(&#\d{2,4};|&(#x[0-9a-fA-F]{2,4}|frac\d\d);)|' .
             '(\d{4}-\d\d-\d\d)|(\d\d\.\d\d\.\d{4})|' .
             '(0[ \d-/]+)|([+-]?\d+)([.,]\d+|))@';
-          $t = preg_replace_callback($number_finder, self::$method, $t);
+          $t = preg_replace_callback($number_finder, $method, $t);
         }
         $result .= $t;
       }
@@ -831,7 +831,7 @@ class SmartyPants {
         $t = $cur_token[1];
         if (!$in_pre) {
           $abbr_finder = '/(?<=\s|)(\p{L}+\.)(\p{L}+\.)+(?=\s|)/u';
-          $t = preg_replace_callback($abbr_finder, self::$replace_method, $t);
+          $t = preg_replace_callback($abbr_finder, $replace_method, $t);
         }
         $result .= $t;
       }

Thanks for porting this module to d8.

migmedia

Issue fork typogrify-3133951

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

migmedia created an issue. See original summary.

migmedia’s picture

Version: 8.x-1.0-rc2 » 8.x-1.x-dev
StatusFileSize
new913 bytes
benjifisher’s picture

Status: Active » Needs review
benjifisher’s picture

Issue summary: View changes
StatusFileSize
new4.54 KB
new3.82 KB
new4.73 KB

I am afraid it is more complicated than that.

I am updating the issue summary, including steps to reproduce the problem. When I follow those steps with an unpatched version of the module, I get a WSOD and the error you describe:

Error: Access to undeclared static property: Drupal\typogrify\SmartyPants::$method in Drupal\typogrify\SmartyPants::smartNumbers() (line 684 of /app/modules/contrib/typogrify/src/SmartyPants.php)

When I apply the patch from Comment #2, I no longer get the WSOD, but the filter is not wrapping the numbers and I see this in the log:

Warning: preg_replace_callback(): Requires argument 2, 'numberNarrownbsp', to be a valid callback in Drupal\typogrify\SmartyPants::smartNumbers() (line 684 of /app/modules/contrib/typogrify/src/SmartyPants.php)

So we need some variant of self::$method to identify the method. One option is to use [__CLASS__, $method]. When I do that, this is what I see in the log:

Warning: preg_replace_callback(): Compilation failed: invalid range in character class at offset 92 in Drupal\typogrify\SmartyPants::smartNumbers() (line 684 of /app/modules/contrib/typogrify/src/SmartyPants.php)

That means there is something wrong with this regular expression:

          $number_finder = '@(?:(&#\d{2,4};|&(#x[0-9a-fA-F]{2,4}|frac\d\d);)|' .
            '(\d{4}-\d\d-\d\d)|(\d\d\.\d\d\.\d{4})|' .
            '(0[ \d-/]+)|([+-]?\d+)([.,]\d+|))@';

I think the invalid range refers to this part: [ \d-/]. Do we know what that regular expression is supposed to do? Is it the same as what we have in the D7 version?

It may be that we are running into differences between PHP 5 and PHP 7, both in the use of self and in regular expressions. Or maybe we just garbled things when updating the code for Drupal 8.

I will start by adding some failing tests and correctly calling the methods. Maybe someone else can help with the regular expression.

I am leaving the status at NW in order to trigger the testbot, but I expect both the test-only patch and the "real" one to fail. The "real" one should generate the last warning quoted above.

Status: Needs review » Needs work

The last submitted patch, 4: 3133951-4.patch, failed testing. View results

benjifisher’s picture

A quick search on the PHP warning led me to preg_match(): Compilation failed: invalid range in character class at offset on StackOverflow. Based on the answer there, the invalid range warning may be due to a change in PHP 7.3. If so, then the patch in #4 may pass. I queued a second test using PHP 7.3, which is the version I am using locally.

If the patch in #4 does pass with PHP 7.1, then I may decide to commit it, since the problem with the regular expression is a separate issue.

benjifisher’s picture

Status: Needs work » Needs review
StatusFileSize
new1.46 KB
new3.86 KB
new4.86 KB

That was quick! The testbot must be stuck at home and bored, just like everyone else.

Let me see if I can fix the regular expression. The automated test was not quite right, so I am attaching an updated test-only patch as well as one that I hope to pass and an interdiff.

The last submitted patch, 7: 3133951-7-test-only.patch, failed testing. View results

benjifisher’s picture

Yay, the testbot approves!

I would like to get some real-world testing, and maybe even a code review, before committing this patch, so I will leave the status at NR. In particular, that change to the regular expression could use a second look.

benjifisher’s picture

Assigned: benjifisher » Unassigned

I forgot to un-assign myself.

wim leers’s picture

  1. +++ b/src/SmartyPants.php
    @@ -680,8 +680,8 @@ class SmartyPants {
    -            '(0[ \d-/]+)|([+-]?\d+)([.,]\d+|))@';
    -          $t = preg_replace_callback($number_finder, self::$method, $t);
    +            '(0[ \d\-/]+)|([+-]?\d+)([.,]\d+|))@';
    +          $t = preg_replace_callback($number_finder, [__CLASS__, $method], $t);
             }
             $result .= $t;
           }
    @@ -831,7 +831,7 @@ class SmartyPants {
    
    @@ -831,7 +831,7 @@ class SmartyPants {
             $t = $cur_token[1];
             if (!$in_pre) {
               $abbr_finder = '/(?<=\s|)(\p{L}+\.)(\p{L}+\.)+(?=\s|)/u';
    -          $t = preg_replace_callback($abbr_finder, self::$replace_method, $t);
    +          $t = preg_replace_callback($abbr_finder, [__CLASS__, $replace_method], $t);
             }
    

    +1 for this syntax.

  2. +++ b/tests/src/Kernel/TypogrifySmartyPantsTest.php
    @@ -18,10 +18,44 @@ class TypogrifySmartyPantsTest extends KernelTestBase {
    +  public function testTypogrify(array $extra_settings, $original, $processed) {
    

    Nit: you could typehint $original and $processed too.

  3. +++ b/tests/src/Kernel/TypogrifySmartyPantsTest.php
    @@ -18,10 +18,44 @@ class TypogrifySmartyPantsTest extends KernelTestBase {
    +    $configuration = ['settings' => $extra_settings + $this->baseSettings];
    

    👍 for $extra_settings as the first operand for the + operator; that means that settings in there always win of those in $this→baseSettings (the second operand).

  4. +++ b/tests/src/Kernel/TypogrifySmartyPantsTest.php
    @@ -18,10 +18,44 @@ class TypogrifySmartyPantsTest extends KernelTestBase {
    +    $this->assertEqual($result, $processed);
    

    🤔 Why assertEqual() and not assertSame()?

  5. +++ b/tests/src/Kernel/TypogrifySmartyPantsTest.php
    @@ -31,29 +65,25 @@ HTML;
    +    // Original example compatibility-test.
    

    Übernit: s/compatibility-test/compatibility test/

  6. +++ b/tests/src/Kernel/TypogrifySmartyPantsTest.php
    @@ -31,29 +65,25 @@ HTML;
    +      'original_typogrify_example' => [
    ...
    +      'test_wrap_abbreviations' => [
    ...
    +      'test_wrap_numbers' => [
    

    FYI @dataProvider test case labels are free form — they can contain spaces and even emoji.

benjifisher’s picture

StatusFileSize
new2.24 KB
new3.86 KB
new4.87 KB

Thanks for the review!

  1. Thanks.

  2. TIL that type hints are now called Type declarations in PHP 7. Checking that page, I see that string is an allowable declaration since PHP 7.0.0. I am a little worried that people might still be running an old version of Drupal 8 that allows PHP 5.6, but since this is in the test I think I will take the suggestion. Fixed.

  3. Yes, I see this pattern a lot in Drupal.

  4. No good reason: it was like that when I got here. I wish the API docs for KernelTestBase had links to the methods in PHPUnit\Framework\Assert, such as assertSame() ans assertEquals(). As in (2) I will not worry too much about BC for the tests. Fixed.

  5. Fixed.

  6. Thanks for the tip. I am not a fan of emojis, but some spaces will be an improvement. While I am at it, I will use the actual option name: wrap_abbr instead of wrap_abbreviations.

The last submitted patch, 12: 3133951-12-test-only.patch, failed testing. View results

Status: Needs review » Needs work

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

benjifisher’s picture

Status: Needs work » Needs review
StatusFileSize
new528 bytes
new3.87 KB
new4.88 KB

It turns out there was a reason to use assertEqual() instead of assertSame().

Since assertEqual() is deprecated (use assertEquals() instead) I will not go back. Instead, cast FilterProcessResult to string.

The last submitted patch, 15: 3133951-15-test-only.patch, failed testing. View results

benjifisher’s picture

StatusFileSize
new1.18 KB
new4.02 KB
new5.03 KB

The attached patch makes two changes to the test:

  1. Use assertEquals() instead of casting to string and then using assertSame().
  2. Test the wrap_numbers option more thoroughly.

You can see the two changes separately in the issue fork.

The first change reverses the decision I made in #15.

Since the problem is caused by an un-escaped - character in a regular expression, I decided that the wrap_numbers test should include that character.

The last submitted patch, 17: 3133951-17-test-only.patch, failed testing. View results

  • benjifisher committed 0eed683 on 8.x-1.x
    Issue #3133951 by benjifisher, migmedia, wimleers: Undeclared static...
benjifisher’s picture

Status: Needs review » Fixed

I did my best to review my own patch with "fresh eyes" before making the additional changes in #17. Fixed.

Status: Fixed » Closed (fixed)

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