Cannot reproduce Server error from pelago/emogrifier when sending email

DragonByte Tech

Well-known member
Affected version
2.1.3
Code:
Server error log

    Error: Call to a member function appendChild() on null src/vendor/pelago/emogrifier/src/Emogrifier.php:1419

    Generated by: Unknown account Jul 26, 2019 at 12:01 AM

Stack trace

#0 src/vendor/pelago/emogrifier/src/Emogrifier.php(1328): Pelago\Emogrifier->addStyleElementToDocument('.listInline.lis...')
#1 src/vendor/pelago/emogrifier/src/Emogrifier.php(526): Pelago\Emogrifier->copyUninlineableCssToStyleNode(Object(DOMXPath), Array)
#2 src/vendor/pelago/emogrifier/src/Emogrifier.php(377): Pelago\Emogrifier->process()
#3 src/XF/Mail/Styler.php(36): Pelago\Emogrifier->emogrify()
#4 src/XF/Mail/Mailer.php(187): XF\Mail\Styler->styleHtml('<p>The Northwes...', false, Object(XF\Language))
#5 src/addons/DBTech/Mail/Job/DigestEmail.php(169): XF\Mail\Mailer->renderMailTemplate('dbtech_mail_dig...', Array, Object(XF\Language), Object(SV\ElasticSearchEssentials\XF\Entity\User))
#6 src/XF/Job/AbstractUserCriteriaJob.php(59): DBTech\Mail\Job\DigestEmail->executeAction(Object(SV\ElasticSearchEssentials\XF\Entity\User))
#7 src/addons/DBTech/Mail/Job/DigestEmail.php(77): XF\Job\AbstractUserCriteriaJob->run(G)
#8 src/XF/Job/Manager.php(253): DBTech\Mail\Job\DigestEmail->run(G)
#9 src/XF/Job/Manager.php(195): XF\Job\Manager->runJobInternal(Array, G)
#10 src/XF/Job/Manager.php(79): XF\Job\Manager->runJobEntry(Array, G)
#11 job.php(42): XF\Job\Manager->runQueue(false, 8)
#12 {main}

DBTech\Mail\Job\DigestEmail is an extension to XF\Job\UserEmail.

This is the relevant block of code from that class:
PHP:
        $renderedMail = $this->app->mailer()->renderMailTemplate(
            'dbtech_mail_digest', [
            'digest' => $digest,
            'content' => $content,
            'user' => $user,
            'brandingVariables' => $this->data['brandingVariables']
        ], $language, $user);

This is the contents of the template email:dbtech_mail_digest:
HTML:
<mail:subject>{$digest.title}</mail:subject>

<xf:if is="$digest.introduction"><p>{$digest.introduction|raw}</p></xf:if>

<xf:if is="$content is not empty">
    <xf:foreach loop="$content" key="$contentType" value="$contentTypeContent">
        {$contentTypeContent|raw}
        
        <hr />
    </xf:foreach>
<xf:else />
    <p>{{ phrase('dbtech_mail_digest_no_content') }}</p>
</xf:if>

<xf:if is="{$user.dbtech_mail_newsletter_frequency} > 1">
    {{ phrase('dbtech_mail_newsletter_reducefrequency_further_link', {
        'param1': '<a href="' . link('canonical:dbtech-mail/digests/reduce-frequency', $digest, {'c': $user.email_confirm_key, 'user_id': $user.user_id}) . '">' . phrase('dbtech_mail_click_here') . '</a>'
    }) }}

    {{ phrase('dbtech_mail_newsletter_increasefrequency_link', {
        'param1': '<a href="' . link('canonical:dbtech-mail/digests/increase-frequency', $digest, {'c': $user.email_confirm_key, 'user_id': $user.user_id}) . '">' . phrase('dbtech_mail_click_here') . '</a>'
    }) }}
<xf:else />
    {{ phrase('dbtech_mail_newsletter_reducefrequency_link', {
        'param1': '<a href="' . link('canonical:dbtech-mail/digests/reduce-frequency', $digest, {'c': $user.email_confirm_key, 'user_id': $user.user_id}) . '">' . phrase('dbtech_mail_click_here') . '</a>'
    }) }}
</xf:if>

<p class="minorText"><a href="{{ link('canonical:dbtech-mail/digests/settings', $digest) }}">{{ phrase('dbtech_mail_configure_digest_content_settings') }}</a></p>

<p class="minorText"><a href="{{ link('canonical:email-stop/content', $user, {'t': 'dbtech_mail_digest', 'id': $digest.digest_id}) }}">{{ phrase('dbtech_mail_newsletter_unsubscribe_link') }}</a></p>

<xf:if is="$brandingVariables">
<p class="footer p-footer-copyright">{$brandingVariables.flavour}
    <a rel="nofollow noopener" href="https://www.dragonbyte-tech.com/store/{$brandingVariables.productid}/?utm_source={$brandingVariables.utm_source}&utm_campaign=product&utm_medium={$brandingVariables.utm_medium}&utm_content={$brandingVariables.utm_content}" target="_blank">{$brandingVariables.title}</a>.
    <br />
    <a rel="nofollow noopener" href="https://www.dragonbyte-tech.com/?utm_source={$brandingVariables.utm_source|raw}&utm_campaign=site&utm_medium={$brandingVariables.utm_medium}&utm_content={$brandingVariables.utm_content}" target="_blank">Copyright &copy; {$brandingVariables.date} DragonByte Technologies Ltd.</a>
</p>
</xf:if>

<xf:page option="template"></xf:page>

<xf:if is="$digest.introduction"><p>{$digest.introduction|raw}</p></xf:if> is the bit that causes #4 in the stack trace above.

Users have tested both using HTML in the digest introduction, and using plaintext, both still cause the error. Removing the introduction text itself appears to fix it.

Please let me know if you require any further information.
 
I've passed it along to the customer who first reported the issue, hopefully they'll get back to you soon.

From what I understand, it doesn't matter if the introduction text uses HTML or plain text, though they'll be able to produce the exact contents of that setting.
 
Probably need to understand what the content of $digest.introduction is specifically.

I think I hit this issue first or am lucky enough to be in the top 3. The text in question was (sans quotes):
"Your Brand New CCF Digest is here. Below are some of the new topics discussed since we shipped off your last digest email. "
 
<xf:if is="$digest.introduction"><p>{$digest.introduction|raw}</p></xf:if> is the bit that causes #4 in the stack trace above.
Actually, this is slightly misleading.

The styleHtml function receives the entirety of the e-mail template (minus anything inside <mail:x> tags).

It is something within the entire HTML source that is triggering it.

Clearly, this isn't something that happens in default circumstances, so we really need a reduced reproduction case because as it stands, we cannot reproduce this.
 
Actually, this is slightly misleading.

The styleHtml function receives the entirety of the e-mail template (minus anything inside <mail:x> tags).

It is something within the entire HTML source that is triggering it.

Clearly, this isn't something that happens in default circumstances, so we really need a reduced reproduction case because as it stands, we cannot reproduce this.
In that case, could you have a look at this template to see if there's anything wrong here?

HTML:
<h2>{{ phrase('threads') }}</h2>

<xf:if is="$content is not empty">
    <div class="digestContent">
        <xf:foreach loop="$content" value="$thread">
            <div class="digestContentBit">
                <h3><a href="{{ link('canonical:threads/unread', $thread, {'new': 1}) }}">{{ prefix('thread', $thread, 'escaped') }}{$thread.title}</a></h3>

                <ul class="listInline listInline--bullet" style="margin-bottom:0">
                    <li class="minorText"><xf:username user="{$thread.User}" class="textLink" /></li>
                    <li class="minorText">{{ date($thread.last_post_date) }}</li>
                    <li class="minorText">{{ phrase('dbtech_mail_x_posts', {'posts': ($thread.reply_count + 1)|number}) }}</li>
                    <li class="minorText">{{ phrase('dbtech_mail_x_views', {'views': $thread.view_count|number}) }}</li>
                    <li class="minorText"><a class="textLink" href="{{ link('canonical:forums', $thread.Forum) }}">{$thread.Forum.title}</a></li>
                </ul>

                <div class="message">
                    <xf:if is="$settings.enable_rich_preview">
                        {{ bb_code_type('emailHtml', snippet($thread.FirstPost.message, 300), 'post', $thread.FirstPost) }}
                    <xf:else />
                        {{ snippet($thread.FirstPost.message, $settings.snippet_length, {'stripQuote': true}) }}
                    </xf:if>
                </div>
            </div>
        </xf:foreach>
    </div>
<xf:else />
    <p>{{ phrase('dbtech_mail_newsletter_no_threads') }}</p>
</xf:if>

<xf:page option="template"></xf:page>

This is the part that's inserted for the {$contentTypeContent|raw} in the original template.
 
If this is reproducible fairly reliably, can you test it without this line?
HTML:
<xf:page option="template"></xf:page>
 
If this is reproducible fairly reliably, can you test it without this line?
HTML:
<xf:page option="template"></xf:page>
Wouldn't that cause the wrapper to be added to this sub-template? :unsure: The template that contains the xf:page tag is rendered in PHP.

I think I understand what that test is getting at; if a template is rendered in PHP and it contains CSS classes, it needs to contain a <style> tag for the emogrifier to inject the CSS into.

If I'm right, that would mean that in theory the issue should be fixed if I rework my code so that the <h2>{{ phrase('threads') }}</h2> template is added to the "wrapper" template using <xf:include template="" /> rather than rendered in PHP.

If that fails, I'll remove the CSS classes and switch to using inline CSS in the hope that fixes it.

I'll post back once I know whether these efforts fixed it.
 
Update: I've not heard back from any of the users so I think at this point it's safe to call this a problem with my implementation rather than a bug, and that it's now been fixed.

Appreciate the nudge towards finding a better solution đź‘Ť
 
Top Bottom