FileMaster
Search
Toggle Dark Mode
Home
/
.
/
wp-content
/
plugins
/
mailpoet
/
lib
/
Statistics
Edit File: GATracking.php
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing namespace MailPoet\Statistics; if (!defined('ABSPATH')) exit; use MailPoet\Entities\NewsletterEntity; use MailPoet\Newsletter\Links\Links as NewsletterLinks; use MailPoet\Settings\TrackingConfig; use MailPoet\Util\Helpers; use MailPoet\Util\SecondLevelDomainNames; use MailPoet\WP\Functions; class GATracking { /** @var SecondLevelDomainNames */ private $secondLevelDomainNames; /** @var NewsletterLinks */ private $newsletterLinks; /** @var Functions */ private $wp; /** @var TrackingConfig */ private $tackingConfig; public function __construct( NewsletterLinks $newsletterLinks, Functions $wp, TrackingConfig $trackingConfig ) { $this->secondLevelDomainNames = new SecondLevelDomainNames(); $this->newsletterLinks = $newsletterLinks; $this->wp = $wp; $this->tackingConfig = $trackingConfig; } public function applyGATracking($renderedNewsletter, NewsletterEntity $newsletter, $internalHost = null) { if (!$this->tackingConfig->isEmailTrackingEnabled()) { return $renderedNewsletter; } if ($newsletter->getType() == NewsletterEntity::TYPE_NOTIFICATION_HISTORY && $newsletter->getParent() instanceof NewsletterEntity) { $parentNewsletter = $newsletter->getParent(); $field = $parentNewsletter->getGaCampaign(); } else { $field = $newsletter->getGaCampaign(); } return $this->addGAParamsToLinks($renderedNewsletter, $field, $internalHost); } private function addGAParamsToLinks($renderedNewsletter, $gaCampaign, $internalHost = null) { // join HTML and TEXT rendered body into a text string $content = Helpers::joinObject($renderedNewsletter); $extractedLinks = $this->newsletterLinks->extract($content); $processedLinks = $this->addParams($extractedLinks, $gaCampaign, $internalHost); list($content, $links) = $this->newsletterLinks->replace($content, $processedLinks); // split the processed body with hashed links back to HTML and TEXT list($renderedNewsletter['html'], $renderedNewsletter['text']) = Helpers::splitObject($content); return $renderedNewsletter; } private function addParams($extractedLinks, $gaCampaign, $internalHost = null) { $processedLinks = []; $params = [ 'utm_source' => 'mailpoet', 'utm_medium' => 'email', 'utm_source_platform' => 'mailpoet', ]; if ($gaCampaign) { $params['utm_campaign'] = $gaCampaign; } $internalHost = $internalHost ?: parse_url(home_url(), PHP_URL_HOST); $internalHost = $this->secondLevelDomainNames->get($internalHost); foreach ($extractedLinks as $extractedLink) { if ($extractedLink['type'] !== NewsletterLinks::LINK_TYPE_URL) { continue; } elseif (strpos((string)parse_url($extractedLink['link'], PHP_URL_HOST), $internalHost) === false) { // Process only internal links (i.e. pointing to current site) continue; } $link = $extractedLink['link']; // Do not overwrite existing query parameters $parsedUrl = parse_url($link); $linkParams = $params; if (isset($parsedUrl['query'])) { foreach (array_keys($params) as $param) { if (strpos($parsedUrl['query'], $param . '=') !== false) { unset($linkParams[$param]); } } } // Extract shortcodes from query parameters to preserve them list($linkWithPlaceholders, $shortcodeMap) = $this->extractShortcodes($link); // Add GA parameters to the link with placeholders $linkWithGAParams = $this->wp->addQueryArg($linkParams, $linkWithPlaceholders); // Restore the original shortcodes $linkWithGAParams = $this->restoreShortcodes($linkWithGAParams, $shortcodeMap); $processedLink = $this->wp->applyFilters( 'mailpoet_ga_tracking_link', $linkWithGAParams, $extractedLink['link'], $linkParams, $extractedLink['type'] ); $processedLinks[$link] = [ 'type' => $extractedLink['type'], 'link' => $link, 'processed_link' => $processedLink, ]; } return $processedLinks; } /** * Extract shortcodes from URL query parameter values and replace them with placeholders. * Shortcodes use the format [shortcode:value|option:value] and should not be URL-encoded. * Only shortcodes that are actual parameter values (or part of them) are extracted. * * @return array [$urlWithPlaceholders, $shortcodeMap] */ private function extractShortcodes(string $url): array { $parsedUrl = parse_url($url); if (!isset($parsedUrl['query'])) { return [$url, []]; } // Parse the query string into parameters to validate shortcodes are in parameter values parse_str($parsedUrl['query'], $params); $shortcodeMap = []; $urlWithPlaceholders = $url; $index = 0; // Process each parameter value (recursively for arrays) $this->processParamsForShortcodes($params, $urlWithPlaceholders, $shortcodeMap, $index); return [$urlWithPlaceholders, $shortcodeMap]; } /** * Process parameter values recursively to find and replace shortcodes. * Handles both string values and nested arrays. * * @param array $params Parameter values to process * @param string $urlWithPlaceholders URL being modified (passed by reference) * @param array $shortcodeMap Map of placeholders to shortcodes (passed by reference) * @param int $index Current placeholder index (passed by reference) */ private function processParamsForShortcodes(array $params, string &$urlWithPlaceholders, array &$shortcodeMap, int &$index): void { foreach ($params as $value) { if (is_array($value)) { // Recursively process array values $this->processParamsForShortcodes($value, $urlWithPlaceholders, $shortcodeMap, $index); } elseif (is_string($value)) { // Find shortcodes in string values // Pattern matches MailPoet shortcodes in the format [name:value|option:value] // - \[ matches opening bracket // - [^\]]{1,400} matches 1-400 characters that are not a closing bracket // (limit prevents ReDoS attacks from catastrophic backtracking) // - \] matches closing bracket // Examples: [subscriber:email], [subscriber:firstname|default:Guest] $pattern = '/\[[^\]]{1,400}\]/'; if (preg_match_all($pattern, $value, $matches)) { foreach ($matches[0] as $shortcode) { // Create a unique placeholder $placeholder = 'MPSHORTCODE' . $index . 'MPEND'; $shortcodeMap[$placeholder] = $shortcode; // Replace shortcode with placeholder directly in the URL $urlWithPlaceholders = str_replace($shortcode, $placeholder, $urlWithPlaceholders); $index++; } } } } } /** * Restore shortcodes in the URL by replacing placeholders with original shortcodes. */ private function restoreShortcodes(string $url, array $shortcodeMap): string { if (empty($shortcodeMap)) { return $url; } foreach ($shortcodeMap as $placeholder => $shortcode) { $url = str_replace($placeholder, $shortcode, $url); } return $url; } }
Save
Back