Email Dynamic Content in 4.x - working for anyone?

Your software
My Mautic version is: 4.2.1, 4.2.2, 4.3.1
My PHP version is: 7.4
Your problem
My problem is:
Dynamic content does not seem to be working for me in any of these versions. Not in HTML or MJML mailers. When I send an email it just comes out Dynamic Content 2, and in 4.3.1 I do not even see the widget of Dynamic content.

So first I am interested in if anyone has it working for them.

@joeyk @rcheesley tagging the two of you as I really need some assistance here and just bringing this question to your attention. :pray:

1 Like

Just tested this on Gitpod and I am seeing the same in that the dynamic content is not working, with the following error:

gitpod /workspace/mautic $ ddev exec bin/console m:c:t
Triggering events for campaign 1
Triggering events for newly added contacts
2 total events(s) to be processed in batches of 100 contacts
 2/2 [============================] 100%PHP Notice:  Trying to access array offset on value of type null in /var/www/html/app/bundles/EmailBundle/EventListener/MatchFilterForLeadTrait.php on line 57

Notice: Trying to access array offset on value of type null in /var/www/html/app/bundles/EmailBundle/EventListener/MatchFilterForLeadTrait.php on line 57
PHP Notice:  Trying to access array offset on value of type null in /var/www/html/app/bundles/EmailBundle/EventListener/MatchFilterForLeadTrait.php on line 57

Notice: Trying to access array offset on value of type null in /var/www/html/app/bundles/EmailBundle/EventListener/MatchFilterForLeadTrait.php on line 57

However I am getting the default content sent each time. I tried this with the Blank theme just to keep it simple.

1 Like

Submitted a bug report here!

1 Like

Hey @rcheesley

I went ahead and got our developer to make a quick fix for this. He did not do it inside the Mautic branch as we just wanted it working. So I have two files, one for Mautic 4.0.x - 4.2.x and a second file for 4.3.x and upwards. Maybe it can be used to help fix the issue in GitHub.

Here are the two pieces of code:
For Mautic 4.0.x - 4.2.x MatchFilterLeadTrait.php

<?php

namespace Mautic\EmailBundle\EventListener;

/**
 * Trait MatchFilterForLeadTrait.
 */
trait MatchFilterForLeadTrait
{
    /**
     * @return bool
     */
    protected function matchFilterForLead(array $filter, array $lead)
    {
        if (empty($lead['id'])) {
            // Lead in generated for preview with faked data
            return false;
        }
        $groups   = [];
        $groupNum = 0;

        foreach ($filter as $data) {
            $isCompanyField = (0 === strpos($data['field'], 'company') && 'company' !== $data['field']);
            $primaryCompany = ($isCompanyField && !empty($lead['companies'])) ? $lead['companies'][0] : null;

            if (!array_key_exists($data['field'], $lead) && !$isCompanyField) {
                continue;
            }

            if(is_null($primaryCompany) && $isCompanyField){
                continue;
            }

            /*
             * Split the filters into groups based on the glue.
             * The first filter and any filters whose glue is
             * "or" will start a new group.
             */
            if (0 === $groupNum || 'or' === $data['glue']) {
                ++$groupNum;
                $groups[$groupNum] = null;
            }

            /*
             * If the group has been marked as false, there
             * is no need to continue checking the others
             * in the group.
             */
            if (false === $groups[$groupNum]) {
                continue;
            }

            /*
             * If we are checking the first filter in a group
             * assume that the group will not match.
             */
            if (null === $groups[$groupNum]) {
                $groups[$groupNum] = false;
            }

            $leadVal   = ($isCompanyField ? $primaryCompany[$data['field']] : $lead[$data['field']]);
            $filterVal = $data['filter'];

            switch ($data['type']) {
                case 'boolean':
                    if (null !== $leadVal) {
                        $leadVal = (bool) $leadVal;
                    }

                    if (null !== $filterVal) {
                        $filterVal = (bool) $filterVal;
                    }
                    break;
                case 'datetime':
                case 'time':
                    $leadValCount   = substr_count($leadVal, ':');
                    $filterValCount = substr_count($filterVal, ':');

                    if (2 === $leadValCount && 1 === $filterValCount) {
                        $filterVal .= ':00';
                    }
                    break;
                case 'tags':
                case 'multiselect':
                    if (!is_array($leadVal)) {
                        $leadVal = explode('|', $leadVal);
                    }

                    if (!is_array($filterVal)) {
                        $filterVal = explode('|', $filterVal);
                    }
                    break;
                case 'number':
                    $leadVal   = (int) $leadVal;
                    $filterVal = (int) $filterVal;
                    break;
                case 'select':
                default:
                    if (is_numeric($leadVal)) {
                        $leadVal   = (int) $leadVal;
                        $filterVal = (int) $filterVal;
                    }
                    break;
            }

            switch ($data['operator']) {
                case '=':
                    if ('boolean' === $data['type']) {
                        $groups[$groupNum] = $leadVal === $filterVal;
                    } else {
                        $groups[$groupNum] = $leadVal == $filterVal;
                    }
                    break;
                case '!=':
                    if ('boolean' === $data['type']) {
                        $groups[$groupNum] = $leadVal !== $filterVal;
                    } else {
                        $groups[$groupNum] = $leadVal != $filterVal;
                    }
                    break;
                case 'gt':
                    $groups[$groupNum] = $leadVal > $filterVal;
                    break;
                case 'gte':
                    $groups[$groupNum] = $leadVal >= $filterVal;
                    break;
                case 'lt':
                    $groups[$groupNum] = $leadVal < $filterVal;
                    break;
                case 'lte':
                    $groups[$groupNum] = $leadVal <= $filterVal;
                    break;
                case 'empty':
                    $groups[$groupNum] = empty($leadVal);
                    break;
                case '!empty':
                    $groups[$groupNum] = !empty($leadVal);
                    break;
                case 'like':
                    $filterVal         = str_replace(['.', '*', '%'], ['\.', '\*', '.*'], $filterVal);
                    $groups[$groupNum] = 1 === preg_match('/' . $filterVal . '/', $leadVal);
                    break;
                case '!like':
                    $filterVal         = str_replace(['.', '*'], ['\.', '\*'], $filterVal);
                    $filterVal         = str_replace('%', '.*', $filterVal);
                    $groups[$groupNum] = 1 !== preg_match('/' . $filterVal . '/', $leadVal);
                    break;
                case 'in':
                    $leadValMatched = false;
                    foreach ($leadVal as $v) {
                        if (in_array($v, $filterVal)) {
                            $leadValMatched = true;
                            // Break once we find a match
                            break;
                        }
                    }
                    $groups[$groupNum] = $leadValMatched;
                    break;
                case '!in':
                    $leadValNotMatched = true;

                    foreach ($leadVal as $v) {
                        if (in_array($v, $filterVal)) {
                            $leadValNotMatched = false;
                            // Break once we find a match
                            break;
                        }
                    }

                    $groups[$groupNum] = $leadValNotMatched;
                    break;
                case 'regexp':
                    $groups[$groupNum] = 1 === preg_match('/' . $filterVal . '/i', $leadVal);
                    break;
                case '!regexp':
                    $groups[$groupNum] = 1 !== preg_match('/' . $filterVal . '/i', $leadVal);
                    break;
                case 'startsWith':
                    $groups[$groupNum] = 0 === strncmp($leadVal, $filterVal, strlen($filterVal));
                    break;
                case 'endsWith':
                    $endOfString       = substr($leadVal, strlen($leadVal) - strlen($filterVal));
                    $groups[$groupNum] = 0 === strcmp($endOfString, $filterVal);
                    break;
                case 'contains':
                    $groups[$groupNum] = false !== strpos((string) $leadVal, (string) $filterVal);
                    break;
            }
        }

        return in_array(true, $groups);
    }
}

And for Mautic 4.3 upwards:

<?php

namespace Mautic\EmailBundle\EventListener;

use Mautic\LeadBundle\Segment\OperatorOptions;

/**
 * Trait MatchFilterForLeadTrait.
 */
trait MatchFilterForLeadTrait
{
    /**
     * @return bool
     */
    protected function matchFilterForLead(array $filter, array $lead)
    {
        if (empty($lead['id'])) {
            // Lead in generated for preview with faked data
            return false;
        }
        $groups   = [];
        $groupNum = 0;

        foreach ($filter as $data) {
            $isCompanyField = (0 === strpos($data['field'], 'company') && 'company' !== $data['field']);
            $primaryCompany = ($isCompanyField && !empty($lead['companies'])) ? $lead['companies'][0] : null;

            if (!array_key_exists($data['field'], $lead) && !$isCompanyField) {
                continue;
            }

            if(is_null($primaryCompany) && $isCompanyField){
                continue;
            }
            /*
             * Split the filters into groups based on the glue.
             * The first filter and any filters whose glue is
             * "or" will start a new group.
             */
            if (0 === $groupNum || 'or' === $data['glue']) {
                ++$groupNum;
                $groups[$groupNum] = null;
            }

            /*
             * If the group has been marked as false, there
             * is no need to continue checking the others
             * in the group.
             */
            if (false === $groups[$groupNum]) {
                continue;
            }

            /*
             * If we are checking the first filter in a group
             * assume that the group will not match.
             */
            if (null === $groups[$groupNum]) {
                $groups[$groupNum] = false;
            }

            $leadVal  = ($isCompanyField) ? $primaryCompany[$data['field']] : $lead[$data['field']];

           
            $filterVal = $data['filter'];

            switch ($data['type']) {
                case 'boolean':
                    if (null !== $leadVal) {
                        $leadVal = (bool) $leadVal;
                    }

                    if (null !== $filterVal) {
                        $filterVal = (bool) $filterVal;
                    }
                    break;
                case 'datetime':
                case 'time':
                    $leadValCount   = substr_count($leadVal, ':');
                    $filterValCount = substr_count($filterVal, ':');

                    if (2 === $leadValCount && 1 === $filterValCount) {
                        $filterVal .= ':00';
                    }
                    break;
                case 'tags':
                case 'multiselect':
                    if (!is_array($leadVal)) {
                        $leadVal = explode('|', $leadVal);
                    }
                    if (!is_array($filterVal)) {
                        $filterVal = explode('|', $filterVal);
                    }
                    break;
                case 'number':
                    $leadVal   = (int) $leadVal;
                    $filterVal = (int) $filterVal;
                    break;
                case 'select':
                    if (!is_array($filterVal)) {
                        $filterVal = explode('|', $filterVal);
                    }
                    break;
                default:
                    if (is_numeric($leadVal)) {
                        $leadVal   = (int) $leadVal;
                        $filterVal = (int) $filterVal;
                    }
                    break;
            }

            switch ($data['operator']) {
                case '=':
                    if ('boolean' === $data['type']) {
                        $groups[$groupNum] = $leadVal === $filterVal;
                    } else {
                        $groups[$groupNum] = $leadVal == $filterVal;
                    }
                    break;
                case '!=':
                    if ('boolean' === $data['type']) {
                        $groups[$groupNum] = $leadVal !== $filterVal;
                    } else {
                        $groups[$groupNum] = $leadVal != $filterVal;
                    }
                    break;
                case 'gt':
                    $groups[$groupNum] = $leadVal > $filterVal;
                    break;
                case 'gte':
                    $groups[$groupNum] = $leadVal >= $filterVal;
                    break;
                case 'lt':
                    $groups[$groupNum] = $leadVal < $filterVal;
                    break;
                case 'lte':
                    $groups[$groupNum] = $leadVal <= $filterVal;
                    break;
                case 'empty':
                    $groups[$groupNum] = empty($leadVal);
                    break;
                case '!empty':
                    $groups[$groupNum] = !empty($leadVal);
                    break;
                case 'like':
                    $filterVal         = str_replace(['.', '*', '%'], ['\.', '\*', '.*'], $filterVal);
                    $groups[$groupNum] = 1 === preg_match('/'.$filterVal.'/', $leadVal);
                    break;
                case '!like':
                    $filterVal         = str_replace(['.', '*'], ['\.', '\*'], $filterVal);
                    $filterVal         = str_replace('%', '.*', $filterVal);
                    $groups[$groupNum] = 1 !== preg_match('/'.$filterVal.'/', $leadVal);
                    break;

                case OperatorOptions::IN:
                    $groups[$groupNum] = $this->checkLeadValueIsInFilter($leadVal, $filterVal, false);
                    break;
                case OperatorOptions::NOT_IN:
                    $groups[$groupNum] = $this->checkLeadValueIsInFilter($leadVal, $filterVal, true);
                    break;
                case 'regexp':
                    $groups[$groupNum] = 1 === preg_match('/'.$filterVal.'/i', $leadVal);
                    break;
                case '!regexp':
                    $groups[$groupNum] = 1 !== preg_match('/'.$filterVal.'/i', $leadVal);
                    break;
                case 'startsWith':
                    $groups[$groupNum] = 0 === strncmp($leadVal, $filterVal, strlen($filterVal));
                    break;
                case 'endsWith':
                    $endOfString       = substr($leadVal, strlen($leadVal) - strlen($filterVal));
                    $groups[$groupNum] = 0 === strcmp($endOfString, $filterVal);
                    break;
                case 'contains':
                    $groups[$groupNum] = false !== strpos((string) $leadVal, (string) $filterVal);
                    break;
            }
        }

        return in_array(true, $groups);
    }

    /**
     * @param mixed $leadVal
     * @param mixed $filterVal
     */
    private function checkLeadValueIsInFilter($leadVal, $filterVal, bool $defaultFlag): bool
    {
        $leadVal    = !is_array($leadVal) ? [$leadVal] : $leadVal;
        $filterVal  = !is_array($filterVal) ? [$filterVal] : $filterVal;
        $retFlag    = $defaultFlag;
        foreach ($leadVal as $v) {
            if (in_array($v, $filterVal)) {
                $retFlag = !$defaultFlag;
                // Break once we find a match
                break;
            }
        }

        return $retFlag;
    }
}

For those that would like to apply the patch you can do the following:

  1. cd /var/www/mautic/app/bundles/EmailBundle/EventListener/
  2. Make a copy of the file MauticFilterForLeadTrait.php → MauticFilterForLeadTrait.php.orig
  3. Download the relevant files above and put it inside this directory.

No need to delete cache. Dynamic Content in html email is now working.

1 Like

Great, thanks!

If they can make a PR against the 4.4 branch that is where the next bugfix release will be coming from.

This topic was automatically closed 36 hours after the last reply. New replies are no longer allowed.