8889841cPKh[\\+SubscriberActivityTracker.phpnu[pageViewCookie = $pageViewCookie; $this->subscriberCookie = $subscriberCookie; $this->subscribersRepository = $subscribersRepository; $this->wp = $wp; $this->wooCommerceHelper = $wooCommerceHelper; $this->trackingConfig = $trackingConfig; } public function trackActivity(): bool { // Don't track in admin interface if ($this->wp->isAdmin()) { return false; } $subscriber = null; $latestTimestamp = $this->getLatestTimestampFromCookie(); // If cookie tracking is not allowed try use last activity from subscriber data if ($latestTimestamp === null) { $subscriber = $this->getSubscriber(); if (!$subscriber) { return false; // Can't determine timestamp } $latestTimestamp = $this->getLatestTimestampFromSubscriber($subscriber); } if ($latestTimestamp + self::TRACK_INTERVAL > $this->wp->currentTime('timestamp')) { return false; } if ($subscriber === null) { $subscriber = $this->getSubscriber(); } if (!$subscriber) { return false; } $this->processTracking($subscriber); return true; } public function registerCallback(string $slug, callable $callback): void { $this->callbacks[$slug] = $callback; } public function unregisterCallback(string $slug): void { unset($this->callbacks[$slug]); } private function processTracking(SubscriberEntity $subscriber): void { $this->subscribersRepository->maybeUpdateLastEngagement($subscriber); $this->pageViewCookie->setPageViewTimestamp($this->wp->currentTime('timestamp')); foreach ($this->callbacks as $callback) { $callback($subscriber); } } private function getLatestTimestampFromCookie(): ?int { if ($this->trackingConfig->isCookieTrackingEnabled()) { return $this->pageViewCookie->getPageViewTimestamp() ?? 0; } return null; } private function getLatestTimestampFromSubscriber(SubscriberEntity $subscriber): int { return $subscriber->getLastEngagementAt() ? $subscriber->getLastEngagementAt()->getTimestamp() : 0; } private function getSubscriber(): ?SubscriberEntity { $wpUser = $this->wp->wpGetCurrentUser(); if ($wpUser->exists()) { return $this->subscribersRepository->findOneBy(['wpUserId' => $wpUser->ID]); } $subscriberId = $this->subscriberCookie->getSubscriberId(); if ($subscriberId) { return $this->subscribersRepository->findOneById($subscriberId); } if (!$this->wooCommerceHelper->isWooCommerceActive()) { return null; } $wooCommerce = $this->wooCommerceHelper->WC(); if (!$wooCommerce || !$wooCommerce->session) { return null; } $customer = $wooCommerce->session->get('customer'); if (!is_array($customer) || empty($customer['email'])) { return null; } return $this->subscribersRepository->findOneBy(['email' => $customer['email']]); } } PKh[#MSubscriberHandler.phpnu[subscriberCookie = $subscriberCookie; $this->subscribersRepository = $subscribersRepository; $this->trackingConfig = $trackingConfig; $this->wp = $wp; } public function identifyByLogin(string $login): void { if (!$this->trackingConfig->isCookieTrackingEnabled()) { return; } $wpUser = $this->wp->getUserBy('login', $login); if ($wpUser) { $this->identifyByEmail($wpUser->user_email); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps } } public function identifyByEmail(string $email): void { if (!$this->trackingConfig->isCookieTrackingEnabled()) { return; } $subscriber = $this->subscribersRepository->findOneBy(['email' => $email]); if ($subscriber) { $this->setCookieBySubscriber($subscriber); } } private function setCookieBySubscriber(SubscriberEntity $subscriber): void { $subscriberId = $subscriber->getId(); if ($subscriberId) { $this->subscriberCookie->setSubscriberId($subscriberId); } } } PKh[QD Unsubscribes.phpnu[sendingQueuesRepository = $sendingQueuesRepository; $this->statisticsUnsubscribesRepository = $statisticsUnsubscribesRepository; $this->subscribersRepository = $subscribersRepository; } public function track( int $subscriberId, string $source, int $queueId = null, string $meta = null, string $method = StatisticsUnsubscribeEntity::METHOD_UNKNOWN ) { $queue = null; $statistics = null; if ($queueId) { $queue = $this->sendingQueuesRepository->findOneById($queueId); } $subscriber = $this->subscribersRepository->findOneById($subscriberId); if (!$subscriber instanceof SubscriberEntity) { return; } if (($queue instanceof SendingQueueEntity)) { $newsletter = $queue->getNewsletter(); if ($newsletter instanceof NewsletterEntity) { $statistics = $this->statisticsUnsubscribesRepository->findOneBy( [ 'queue' => $queue, 'newsletter' => $newsletter, 'subscriber' => $subscriber, ] ); if (!$statistics) { $statistics = new StatisticsUnsubscribeEntity($newsletter, $queue, $subscriber); } } } if ($statistics === null) { $statistics = new StatisticsUnsubscribeEntity(null, null, $subscriber); } if ($meta !== null) { $statistics->setMeta($meta); } $statistics->setSource($source); $statistics->setMethod($method); $this->statisticsUnsubscribesRepository->persist($statistics); $this->statisticsUnsubscribesRepository->flush(); } } PKh[ Clicks.phpnu[cookies = $cookies; $this->subscriberCookie = $subscriberCookie; $this->shortcodes = $shortcodes; $this->linkShortcodeCategory = $linkShortcodeCategory; $this->opens = $opens; $this->statisticsClicksRepository = $statisticsClicksRepository; $this->userAgentsRepository = $userAgentsRepository; $this->subscribersRepository = $subscribersRepository; $this->trackingConfig = $trackingConfig; } /** * @param \stdClass|null $data */ public function track($data) { if (!$data || empty($data->link)) { return $this->abort(); } /** @var SubscriberEntity $subscriber */ $subscriber = $data->subscriber; /** @var SendingQueueEntity $queue */ $queue = $data->queue; /** @var NewsletterEntity $newsletter */ $newsletter = $data->newsletter; /** @var NewsletterLinkEntity $link */ $link = $data->link; $wpUserPreview = ($data->preview && ($subscriber->isWPUser())); // log statistics only if the action did not come from // a WP user previewing the newsletter if (!$wpUserPreview) { $userAgent = !empty($data->userAgent) ? $this->userAgentsRepository->findOrCreate($data->userAgent) : null; $statisticsClicks = $this->statisticsClicksRepository->createOrUpdateClickCount( $link, $subscriber, $newsletter, $queue, $userAgent ); if ( $userAgent instanceof UserAgentEntity && ($userAgent->getUserAgentType() === UserAgentEntity::USER_AGENT_TYPE_HUMAN || $statisticsClicks->getUserAgentType() === UserAgentEntity::USER_AGENT_TYPE_MACHINE) ) { $statisticsClicks->setUserAgent($userAgent); $statisticsClicks->setUserAgentType($userAgent->getUserAgentType()); } $this->statisticsClicksRepository->flush(); $this->sendRevenueCookie($statisticsClicks); $subscriberId = $subscriber->getId(); if ($subscriberId) { $this->subscriberCookie->setSubscriberId($subscriberId); } // track open event $this->opens->track($data, $displayImage = false); // Update engagement date $this->subscribersRepository->maybeUpdateLastEngagement($subscriber); } $url = $this->processUrl($link->getUrl(), $newsletter, $subscriber, $queue, $wpUserPreview); $this->redirectToUrl($url); } private function sendRevenueCookie(StatisticsClickEntity $clicks) { if ($this->trackingConfig->isCookieTrackingEnabled()) { $this->cookies->set( self::REVENUE_TRACKING_COOKIE_NAME, [ 'statistics_clicks' => $clicks->getId(), 'created_at' => time(), ], [ 'expires' => time() + self::REVENUE_TRACKING_COOKIE_EXPIRY, 'path' => '/', ] ); } } public function processUrl( string $url, NewsletterEntity $newsletter, SubscriberEntity $subscriber, SendingQueueEntity $queue, bool $wpUserPreview ) { if (preg_match('/\[link:(?P.*?)\]/', $url, $shortcode)) { if (!$shortcode['action']) $this->abort(); $url = $this->linkShortcodeCategory->processShortcodeAction( $shortcode['action'], $newsletter, $subscriber, $queue, $wpUserPreview ); } else { $this->shortcodes->setQueue($queue); $this->shortcodes->setNewsletter($newsletter); $this->shortcodes->setSubscriber($subscriber); $this->shortcodes->setWpUserPreview($wpUserPreview); $url = $this->shortcodes->replace($url); } return $url; } public function abort() { global $wp_query;// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps WPFunctions::get()->statusHeader(404); $wp_query->set_404();// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps WPFunctions::get()->getTemplatePart((string)404); exit; } public function redirectToUrl($url) { header('Location: ' . $url, true, 302); exit; } } PKh[ Opens.phpnu[statisticsOpensRepository = $statisticsOpensRepository; $this->userAgentsRepository = $userAgentsRepository; $this->subscribersRepository = $subscribersRepository; } public function track($data, $displayImage = true) { if (!$data) { return $this->returnResponse($displayImage); } /** @var SubscriberEntity $subscriber */ $subscriber = $data->subscriber; /** @var SendingQueueEntity $queue */ $queue = $data->queue; /** @var NewsletterEntity $newsletter */ $newsletter = $data->newsletter; $wpUserPreview = ($data->preview && ($subscriber->isWPUser())); // log statistics only if the action did not come from // a WP user previewing the newsletter if (!$wpUserPreview) { $oldStatistics = $this->statisticsOpensRepository->findOneBy([ 'subscriber' => $subscriber->getId(), 'newsletter' => $newsletter->getId(), 'queue' => $queue->getId(), ]); // Open was already tracked if ($oldStatistics) { if (!empty($data->userAgent)) { $userAgent = $this->userAgentsRepository->findOrCreate($data->userAgent); if ( $userAgent->getUserAgentType() === UserAgentEntity::USER_AGENT_TYPE_HUMAN || $oldStatistics->getUserAgentType() === UserAgentEntity::USER_AGENT_TYPE_MACHINE ) { $oldStatistics->setUserAgent($userAgent); $oldStatistics->setUserAgentType($userAgent->getUserAgentType()); $this->statisticsOpensRepository->flush(); } } $this->subscribersRepository->maybeUpdateLastEngagement($subscriber); return $this->returnResponse($displayImage); } $statistics = new StatisticsOpenEntity($newsletter, $queue, $subscriber); if (!empty($data->userAgent)) { $userAgent = $this->userAgentsRepository->findOrCreate($data->userAgent); $statistics->setUserAgent($userAgent); $statistics->setUserAgentType($userAgent->getUserAgentType()); } $this->statisticsOpensRepository->persist($statistics); $this->statisticsOpensRepository->flush(); $this->subscribersRepository->maybeUpdateLastEngagement($subscriber); $this->statisticsOpensRepository->recalculateSubscriberScore($subscriber); } return $this->returnResponse($displayImage); } public function returnResponse($displayImage) { if (!$displayImage) return; // return 1x1 pixel transparent gif image header('Content-Type: image/gif'); // Output of base64_decode is predetermined and safe in this case // phpcs:ignore WordPressDotOrg.sniffs.OutputEscaping.UnescapedOutputParameter, WordPress.Security.EscapeOutput.OutputNotEscaped echo base64_decode('R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw=='); exit; } } PKh[\SubscriberCookie.phpnu[cookies = $cookies; $this->trackingConfig = $trackingConfig; } public function getSubscriberId(): ?int { if (!$this->trackingConfig->isCookieTrackingEnabled()) { return null; } $subscriberId = $this->getSubscriberIdFromCookie(self::COOKIE_NAME); if ($subscriberId) { return $subscriberId; } $subscriberId = $this->getSubscriberIdFromCookie(self::COOKIE_NAME_LEGACY); if ($subscriberId) { $this->setSubscriberId($subscriberId); $this->cookies->delete(self::COOKIE_NAME_LEGACY); return $subscriberId; } return null; } public function setSubscriberId(int $subscriberId): void { if (!$this->trackingConfig->isCookieTrackingEnabled()) { return; } $this->cookies->set( self::COOKIE_NAME, ['subscriber_id' => $subscriberId], [ 'expires' => time() + self::COOKIE_EXPIRY, 'path' => '/', ] ); } private function getSubscriberIdFromCookie(string $cookieName): ?int { $data = $this->cookies->get($cookieName); return is_array($data) && $data['subscriber_id'] ? (int)$data['subscriber_id'] : null; } } PKh[ index.phpnu[cookies = $cookies; $this->trackingConfig = $trackingConfig; } public function getPageViewTimestamp(): ?int { if (!$this->trackingConfig->isCookieTrackingEnabled()) { return null; } return $this->getTimestampCookie(self::COOKIE_NAME); } public function setPageViewTimestamp(int $timestamp): void { if (!$this->trackingConfig->isCookieTrackingEnabled()) { return; } $this->cookies->set( self::COOKIE_NAME, ['timestamp' => $timestamp], [ 'expires' => time() + self::COOKIE_EXPIRY, 'path' => '/', ] ); } private function getTimestampCookie(string $cookieName): ?int { $data = $this->cookies->get($cookieName); return is_array($data) && $data['timestamp'] ? (int)$data['timestamp'] : null; } } PKh[RssWooCommercePurchases.phpnu[woocommerceHelper = $woocommerceHelper; $this->cookies = $cookies; $this->statisticsWooCommercePurchasesRepository = $statisticsWooCommercePurchasesRepository; $this->statisticsClicksRepository = $statisticsClicksRepository; $this->subscribersRepository = $subscribersRepository; $this->subscriberHandler = $subscriberHandler; } public function trackPurchase($id, $useCookies = true) { $order = $this->woocommerceHelper->wcGetOrder($id); if (!$order instanceof WC_Order) { return; } // limit clicks to 'USE_CLICKS_SINCE_DAYS_AGO' range before order has been created $fromDate = $order->get_date_created(); if (is_null($fromDate)) { return; } $from = clone $fromDate; $from->modify(-self::USE_CLICKS_SINCE_DAYS_AGO . ' days'); $to = $order->get_date_created(); if (is_null($to)) { return; } // track purchases from all clicks matched by order email $processedNewsletterIdsMap = []; $orderEmailClicks = $this->getClicks($order->get_billing_email(), $from, $to); foreach ($orderEmailClicks as $click) { $this->statisticsWooCommercePurchasesRepository->createOrUpdateByClickDataAndOrder($click, $order); $newsletter = $click->getNewsletter(); if (!$newsletter instanceof NewsletterEntity) continue; $processedNewsletterIdsMap[$newsletter->getId()] = true; } // try to find a subscriber by order email and start tracking $this->subscriberHandler->identifyByEmail($order->get_billing_email()); if (!$useCookies) { return; } // track purchases from clicks matched by cookie email (only for newsletters not tracked by order) $cookieEmailClicks = $this->getClicks($this->getSubscriberEmailFromCookie(), $from, $to); foreach ($cookieEmailClicks as $click) { $newsletter = $click->getNewsletter(); if (!$newsletter instanceof NewsletterEntity) continue; if (isset($processedNewsletterIdsMap[$newsletter->getId()])) { continue; // do not track click for newsletters that were already tracked by order email } $this->statisticsWooCommercePurchasesRepository->createOrUpdateByClickDataAndOrder($click, $order); } } /** * @param ?string $email * @param \DateTimeInterface $from * @param \DateTimeInterface $to * @return StatisticsClickEntity[] */ private function getClicks(?string $email, \DateTimeInterface $from, \DateTimeInterface $to): array { if (!$email) return []; $subscriber = $this->subscribersRepository->findOneBy(['email' => $email]); if (!$subscriber instanceof SubscriberEntity) { return []; } return $this->statisticsClicksRepository->findLatestPerNewsletterBySubscriber($subscriber, $from, $to); } private function getSubscriberEmailFromCookie(): ?string { $cookieData = $this->cookies->get(Clicks::REVENUE_TRACKING_COOKIE_NAME); if (!$cookieData) { return null; } try { $click = $this->statisticsClicksRepository->findOneById($cookieData['statistics_clicks']); } catch (\Exception $e) { return null; } if (!$click instanceof StatisticsClickEntity) { return null; } $subscriber = $click->getSubscriber(); if ($subscriber instanceof SubscriberEntity) { return $subscriber->getEmail(); } return null; } } PKh[\\+SubscriberActivityTracker.phpnu[PKh[#MSubscriberHandler.phpnu[PKh[QD Unsubscribes.phpnu[PKh[ h!Clicks.phpnu[PKh[ 8Opens.phpnu[PKh[\GSubscriberCookie.phpnu[PKh[ Nindex.phpnu[PKh[446OPageViewCookie.phpnu[PKh[RssTWooCommercePurchases.phpnu[PK gg