Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PageBundle\Model;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\PageBundle\Entity\Page;
|
||||
use Mautic\PageBundle\Entity\PageDraft;
|
||||
use Mautic\PageBundle\Entity\PageDraftRepository;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class PageDraftModel
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private PageDraftRepository $pageDraftRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createDraft(Page $page, string $html, string $template, bool $publicPreview = true): PageDraft
|
||||
{
|
||||
$pageDraft = $this->pageDraftRepository->findOneBy(['page' => $page]);
|
||||
if (!is_null($pageDraft)) {
|
||||
throw new \Exception(sprintf('Draft already exists for page %d', $page->getId()));
|
||||
}
|
||||
$pageDraft = new PageDraft($page, $html, $template, $publicPreview);
|
||||
|
||||
$this->entityManager->persist($pageDraft);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $pageDraft;
|
||||
}
|
||||
|
||||
public function saveDraft(PageDraft $pageDraft): void
|
||||
{
|
||||
$this->entityManager->persist($pageDraft);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
public function deleteDraft(Page $page): void
|
||||
{
|
||||
if (is_null($pageDraft = $page->getDraft())) {
|
||||
throw new NotFoundHttpException(sprintf('Draft not found for page %d', $page->getId()));
|
||||
}
|
||||
$this->entityManager->remove($pageDraft);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
public function getEntity(int $id): ?PageDraft
|
||||
{
|
||||
return $this->pageDraftRepository->find($id);
|
||||
}
|
||||
|
||||
public function getPermissionBase(): string
|
||||
{
|
||||
return 'page:pages';
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PageBundle\Model;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\UrlHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Model\FormModel;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Shortener\Shortener;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\PageBundle\Entity\Redirect;
|
||||
use Mautic\PageBundle\Entity\RedirectRepository;
|
||||
use Mautic\PageBundle\Event\RedirectGenerationEvent;
|
||||
use Mautic\PageBundle\PageEvents;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
/**
|
||||
* @extends FormModel<Redirect>
|
||||
*/
|
||||
class RedirectModel extends FormModel
|
||||
{
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
CorePermissions $security,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
UrlGeneratorInterface $router,
|
||||
Translator $translator,
|
||||
UserHelper $userHelper,
|
||||
LoggerInterface $mauticLogger,
|
||||
CoreParametersHelper $coreParametersHelper,
|
||||
private Shortener $shortener,
|
||||
) {
|
||||
parent::__construct($em, $security, $dispatcher, $router, $translator, $userHelper, $mauticLogger, $coreParametersHelper);
|
||||
}
|
||||
|
||||
public function getRepository(): RedirectRepository
|
||||
{
|
||||
return $this->em->getRepository(Redirect::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Redirect|null
|
||||
*/
|
||||
public function getRedirectById($identifier)
|
||||
{
|
||||
return $this->getRepository()->findOneBy(['redirectId' => $identifier]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a Mautic redirect/passthrough URL.
|
||||
*
|
||||
* @param array $clickthrough
|
||||
* @param bool $shortenUrl
|
||||
* @param array $utmTags
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateRedirectUrl(Redirect $redirect, $clickthrough = [], $shortenUrl = false, $utmTags = [])
|
||||
{
|
||||
if ($this->dispatcher->hasListeners(PageEvents::ON_REDIRECT_GENERATE)) {
|
||||
$event = new RedirectGenerationEvent($redirect, $clickthrough);
|
||||
$this->dispatcher->dispatch($event, PageEvents::ON_REDIRECT_GENERATE);
|
||||
|
||||
$clickthrough = $event->getClickthrough();
|
||||
}
|
||||
|
||||
$url = $this->buildUrl(
|
||||
'mautic_url_redirect',
|
||||
['redirectId' => $redirect->getRedirectId()],
|
||||
true,
|
||||
$clickthrough
|
||||
);
|
||||
|
||||
if (!empty($utmTags)) {
|
||||
$utmTags = $this->getUtmTagsForUrl($utmTags);
|
||||
$appendUtmString = http_build_query($utmTags, '', '&');
|
||||
$url = UrlHelper::appendQueryToUrl($url, $appendUtmString);
|
||||
}
|
||||
|
||||
if ($shortenUrl) {
|
||||
$url = $this->shortener->shortenUrl($url);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate UTMs params for url.
|
||||
*/
|
||||
public function getUtmTagsForUrl($rawUtmTags): array
|
||||
{
|
||||
$utmTags = [];
|
||||
foreach ($rawUtmTags as $utmTag => $value) {
|
||||
$utmTags[str_replace('utm', 'utm_', strtolower($utmTag))] = $value;
|
||||
}
|
||||
|
||||
return $utmTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Redirect entity by URL.
|
||||
*
|
||||
* Use Mautic\PageBundle\Model\TrackableModel::getTrackableByUrl() if associated with a channel
|
||||
*
|
||||
* @return Redirect|null
|
||||
*/
|
||||
public function getRedirectByUrl($url)
|
||||
{
|
||||
// Ensure the URL saved to the database does not have encoded ampersands
|
||||
$url = UrlHelper::decodeAmpersands($url);
|
||||
|
||||
$repo = $this->getRepository();
|
||||
$redirect = $repo->findOneBy(['url' => $url]);
|
||||
|
||||
if (null == $redirect) {
|
||||
$redirect = $this->createRedirectEntity($url);
|
||||
}
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Redirect entities by an array of URLs.
|
||||
*
|
||||
* @return array<Redirect>
|
||||
*/
|
||||
public function getRedirectsByUrls(array $urls)
|
||||
{
|
||||
/** @var array<Redirect> $redirects */
|
||||
$redirects = $this->getRepository()->findByUrls(array_values($urls));
|
||||
$newEntities = [];
|
||||
|
||||
/** @var array<string, Redirect> $return */
|
||||
$return = [];
|
||||
|
||||
/** @var array<string, Redirect> $byUrl */
|
||||
$byUrl = [];
|
||||
|
||||
foreach ($redirects as $redirect) {
|
||||
$byUrl[$redirect->getUrl()] = $redirect;
|
||||
}
|
||||
|
||||
foreach ($urls as $key => $url) {
|
||||
if (empty($url)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($byUrl[$url])) {
|
||||
$return[$key] = $byUrl[$url];
|
||||
} else {
|
||||
$redirect = $this->createRedirectEntity($url);
|
||||
$newEntities[] = $redirect;
|
||||
$return[$key] = $redirect;
|
||||
}
|
||||
}
|
||||
|
||||
// Save new entities
|
||||
if (count($newEntities)) {
|
||||
$this->getRepository()->saveEntities($newEntities);
|
||||
}
|
||||
|
||||
unset($redirects, $newEntities, $byUrl);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Redirect entity for URL.
|
||||
*
|
||||
* @return Redirect
|
||||
*/
|
||||
public function createRedirectEntity($url)
|
||||
{
|
||||
$redirect = new Redirect();
|
||||
$redirect->setUrl($url);
|
||||
$redirect->setRedirectId();
|
||||
|
||||
$this->setTimestamps($redirect, true);
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,892 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PageBundle\Model;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\UrlHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Model\AbstractCommonModel;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Entity\LeadFieldRepository;
|
||||
use Mautic\LeadBundle\Helper\TokenHelper;
|
||||
use Mautic\PageBundle\Entity\Redirect;
|
||||
use Mautic\PageBundle\Entity\Trackable;
|
||||
use Mautic\PageBundle\Event\UntrackableUrlsEvent;
|
||||
use Mautic\PageBundle\PageEvents;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
/**
|
||||
* @extends AbstractCommonModel<Trackable>
|
||||
*/
|
||||
class TrackableModel extends AbstractCommonModel
|
||||
{
|
||||
/**
|
||||
* Array of URLs and/or tokens that should not be converted to trackables.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $doNotTrack = [];
|
||||
|
||||
/**
|
||||
* Tokens with values that could be used as URLs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $contentTokens = [];
|
||||
|
||||
/**
|
||||
* Stores content that needs to be replaced when URLs are parsed out of content.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $contentReplacements = [];
|
||||
|
||||
/**
|
||||
* Used to rebuild correct URLs when the tokenized URL contains query parameters.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $usingClickthrough = true;
|
||||
|
||||
private ?array $contactFieldUrlTokens = null;
|
||||
|
||||
public function __construct(
|
||||
protected RedirectModel $redirectModel,
|
||||
private LeadFieldRepository $leadFieldRepository,
|
||||
EntityManagerInterface $em,
|
||||
CorePermissions $security,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
UrlGeneratorInterface $router,
|
||||
Translator $translator,
|
||||
UserHelper $userHelper,
|
||||
LoggerInterface $mauticLogger,
|
||||
CoreParametersHelper $coreParametersHelper,
|
||||
) {
|
||||
parent::__construct($em, $security, $dispatcher, $router, $translator, $userHelper, $mauticLogger, $coreParametersHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Mautic\PageBundle\Entity\TrackableRepository
|
||||
*/
|
||||
public function getRepository()
|
||||
{
|
||||
return $this->em->getRepository(Trackable::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RedirectModel
|
||||
*/
|
||||
protected function getRedirectModel()
|
||||
{
|
||||
return $this->redirectModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $clickthrough
|
||||
* @param bool|false $shortenUrl If true, use the configured shortener service to shorten the URLs
|
||||
* @param array $utmTags
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateTrackableUrl(Trackable $trackable, $clickthrough = [], $shortenUrl = false, $utmTags = [])
|
||||
{
|
||||
if (!isset($clickthrough['channel'])) {
|
||||
$clickthrough['channel'] = [$trackable->getChannel() => $trackable->getChannelId()];
|
||||
}
|
||||
|
||||
$redirect = $trackable->getRedirect();
|
||||
|
||||
return $this->getRedirectModel()->generateRedirectUrl($redirect, $clickthrough, $shortenUrl, $utmTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a channel Trackable entity by URL.
|
||||
*
|
||||
* @return Trackable|null
|
||||
*/
|
||||
public function getTrackableByUrl($url, $channel, $channelId)
|
||||
{
|
||||
if (empty($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ensure the URL saved to the database does not have encoded ampersands
|
||||
$url = UrlHelper::decodeAmpersands($url);
|
||||
|
||||
$trackable = $this->getRepository()->findByUrl($url, $channel, $channelId);
|
||||
if (null == $trackable) {
|
||||
$trackable = $this->createTrackableEntity($url, $channel, $channelId);
|
||||
$this->getRepository()->saveEntity($trackable->getRedirect());
|
||||
$this->getRepository()->saveEntity($trackable);
|
||||
}
|
||||
|
||||
return $trackable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Trackable entities by an array of URLs.
|
||||
*
|
||||
* @return array<Trackable>
|
||||
*/
|
||||
public function getTrackablesByUrls($urls, $channel, $channelId)
|
||||
{
|
||||
$uniqueUrls = array_unique(
|
||||
array_values($urls)
|
||||
);
|
||||
|
||||
$trackables = $this->getRepository()->findByUrls(
|
||||
$uniqueUrls,
|
||||
$channel,
|
||||
$channelId
|
||||
);
|
||||
|
||||
$newRedirects = [];
|
||||
$newTrackables = [];
|
||||
|
||||
/** @var array<Trackable> $return */
|
||||
$return = [];
|
||||
|
||||
/** @var array<string, Trackable> $byUrl */
|
||||
$byUrl = [];
|
||||
|
||||
/** @var Trackable $trackable */
|
||||
foreach ($trackables as $trackable) {
|
||||
$url = $trackable->getRedirect()->getUrl();
|
||||
$byUrl[$url] = $trackable;
|
||||
}
|
||||
|
||||
foreach ($urls as $key => $url) {
|
||||
if (empty($url)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($byUrl[$url])) {
|
||||
$return[$key] = $byUrl[$url];
|
||||
} else {
|
||||
$trackable = $this->createTrackableEntity($url, $channel, $channelId);
|
||||
// Redirect has to be saved first to have ID available
|
||||
$newRedirects[] = $trackable->getRedirect();
|
||||
$newTrackables[] = $trackable;
|
||||
$return[$key] = $trackable;
|
||||
// Keep track so it can be re-used if applicable
|
||||
$byUrl[$url] = $trackable;
|
||||
}
|
||||
}
|
||||
|
||||
// Save new entities
|
||||
if (count($newRedirects)) {
|
||||
$this->getRepository()->saveEntities($newRedirects);
|
||||
}
|
||||
if (count($newTrackables)) {
|
||||
$this->getRepository()->saveEntities($newTrackables);
|
||||
}
|
||||
|
||||
unset($trackables, $newRedirects, $newTrackables, $byUrl);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of URLs that are tracked by a specific channel.
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getTrackableList($channel, $channelId): array
|
||||
{
|
||||
return $this->getRepository()->findByChannel($channel, $channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of tokens and/or URLs that should not be converted to trackables.
|
||||
*
|
||||
* @param string|string[]|null $content
|
||||
*/
|
||||
public function getDoNotTrackList($content): array
|
||||
{
|
||||
/** @var UntrackableUrlsEvent $event */
|
||||
$event = $this->dispatcher->dispatch(
|
||||
new UntrackableUrlsEvent($content),
|
||||
PageEvents::REDIRECT_DO_NOT_TRACK
|
||||
);
|
||||
|
||||
return $event->getDoNotTrackList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract URLs from content and return as trackables.
|
||||
*
|
||||
* @param string|string[] $content
|
||||
* @param string[] $contentTokens
|
||||
* @param ?string $channel
|
||||
* @param ?int $channelId
|
||||
* @param bool $usingClickthrough Set to false if not using a clickthrough parameter.
|
||||
* This is to ensure that URLs are built correctly with ? or & for
|
||||
* URLs tracked that include query parameters
|
||||
*
|
||||
* @return array{string|string[],Redirect[]|Trackable[]}
|
||||
*/
|
||||
public function parseContentForTrackables($content, array $contentTokens = [], $channel = null, $channelId = null, $usingClickthrough = true): array
|
||||
{
|
||||
$this->usingClickthrough = $usingClickthrough;
|
||||
|
||||
// Set do not track list for validateUrlIsTrackable()
|
||||
$this->doNotTrack = $this->getDoNotTrackList($content);
|
||||
|
||||
// Set content tokens used by validateUrlIsTrackable()
|
||||
$this->contentTokens = $contentTokens;
|
||||
|
||||
$contentWasString = false;
|
||||
if (!is_array($content)) {
|
||||
$contentWasString = true;
|
||||
$content = [$content];
|
||||
}
|
||||
|
||||
$trackableTokens = [];
|
||||
foreach ($content as $key => $text) {
|
||||
$content[$key] = $this->parseContent($text, $channel, $channelId, $trackableTokens);
|
||||
}
|
||||
|
||||
return [
|
||||
$contentWasString ? $content[0] : $content,
|
||||
$trackableTokens,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts array of Trackable or Redirect entities into {trackable} tokens.
|
||||
*
|
||||
* @param array<string, Trackable|Redirect> $entities
|
||||
*
|
||||
* @return array<string, Redirect|Trackable>
|
||||
*/
|
||||
protected function createTrackingTokens(array $entities): array
|
||||
{
|
||||
$tokens = [];
|
||||
foreach ($entities as $trackable) {
|
||||
$redirect = ($trackable instanceof Trackable) ? $trackable->getRedirect() : $trackable;
|
||||
$token = '{trackable='.$redirect->getRedirectId().'}';
|
||||
$tokens[$token] = $trackable;
|
||||
|
||||
// Store the URL to be replaced by a token
|
||||
$this->contentReplacements['second_pass'][$redirect->getUrl()] = $token;
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares content for tokenized trackable URLs by replacing them with {trackable=ID} tokens.
|
||||
*
|
||||
* @param string $content
|
||||
* @param string $type html|text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function prepareContentWithTrackableTokens($content, $type)
|
||||
{
|
||||
if (empty($content)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Simple search and replace to remove attributes, schema for tokens, and updating URL parameter order
|
||||
$firstPassSearch = array_keys($this->contentReplacements['first_pass']);
|
||||
$firstPassReplace = $this->contentReplacements['first_pass'];
|
||||
$content = str_ireplace($firstPassSearch, $firstPassReplace, $content);
|
||||
|
||||
// Sort longer to shorter strings to ensure that URLs that share the same base are appropriately replaced
|
||||
uksort($this->contentReplacements['second_pass'], fn ($a, $b): int => strlen($b) - strlen($a));
|
||||
|
||||
if ('html' == $type) {
|
||||
// For HTML, replace only the links; leaving the link text (if a URL) intact
|
||||
foreach ($this->contentReplacements['second_pass'] as $search => $replace) {
|
||||
// Make the search regular expression match both "&" and "&".
|
||||
$search = preg_quote($search, '/');
|
||||
$search = str_replace('&', '&', $search);
|
||||
$search = str_replace('&', '(?:&|&)', $search);
|
||||
$content = preg_replace(
|
||||
'/<(.*?) href=(["\'])'.$search.'(.*?)\\2(.*?)>/i',
|
||||
'<$1 href=$2'.$replace.'$3$2$4>',
|
||||
$content
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// For text, just do a simple search/replace
|
||||
$secondPassSearch = array_keys($this->contentReplacements['second_pass']);
|
||||
$secondPassReplace = $this->contentReplacements['second_pass'];
|
||||
$content = str_ireplace($secondPassSearch, $secondPassReplace, $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function extractTrackablesFromContent($content)
|
||||
{
|
||||
if (0 !== preg_match('/<[^<]+>/', $content)) {
|
||||
// Parse as HTML
|
||||
$trackableUrls = $this->extractTrackablesFromHtml($content);
|
||||
} else {
|
||||
// Parse as plain text
|
||||
$trackableUrls = $this->extractTrackablesFromText($content);
|
||||
}
|
||||
|
||||
return $trackableUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find URLs in HTML and parse into trackables.
|
||||
*
|
||||
* @param string $html HTML content
|
||||
*/
|
||||
protected function extractTrackablesFromHtml($html): array
|
||||
{
|
||||
// Find links using DOM to only find <a> tags
|
||||
$libxmlPreviousState = libxml_use_internal_errors(true);
|
||||
libxml_use_internal_errors(true);
|
||||
$dom = new \DOMDocument();
|
||||
$dom->loadHTML('<?xml encoding="UTF-8">'.$html);
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors($libxmlPreviousState);
|
||||
$links = $dom->getElementsByTagName('a');
|
||||
|
||||
$xpath = new \DOMXPath($dom);
|
||||
$maps = $xpath->query('//map/area');
|
||||
|
||||
return array_merge($this->extractTrackables($links), $this->extractTrackables($maps));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find URLs in plain text and parse into trackables.
|
||||
*
|
||||
* @param string $text Plain text content
|
||||
*/
|
||||
protected function extractTrackablesFromText($text): array
|
||||
{
|
||||
// Remove any HTML tags (such as img) that could contain href or src attributes prior to parsing for links
|
||||
$text = strip_tags($text);
|
||||
|
||||
// Get a list of URL type contact fields
|
||||
$allUrls = UrlHelper::getUrlsFromPlaintext($text, $this->getContactFieldUrlTokens());
|
||||
$trackableUrls = [];
|
||||
|
||||
foreach ($allUrls as $url) {
|
||||
if ($preparedUrl = $this->prepareUrlForTracking($url)) {
|
||||
[$urlKey, $urlValue] = $preparedUrl;
|
||||
$trackableUrls[$urlKey] = $urlValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $trackableUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Trackable entity.
|
||||
*/
|
||||
protected function createTrackableEntity($url, $channel, $channelId): Trackable
|
||||
{
|
||||
$redirect = $this->getRedirectModel()->createRedirectEntity($url);
|
||||
|
||||
$trackable = new Trackable();
|
||||
$trackable->setChannel($channel)
|
||||
->setChannelId($channelId)
|
||||
->setRedirect($redirect);
|
||||
|
||||
return $trackable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and parse link for tracking.
|
||||
*
|
||||
* @return bool|non-empty-array<mixed, mixed>
|
||||
*/
|
||||
protected function prepareUrlForTracking(string $url)
|
||||
{
|
||||
// Ensure it's clean
|
||||
$url = trim($url);
|
||||
|
||||
// Ensure ampersands are & for the sake of parsing
|
||||
$url = UrlHelper::decodeAmpersands($url);
|
||||
|
||||
// If this is just a token, validate it's supported before going further
|
||||
if (preg_match('/^{.*?}$/i', $url) && !$this->validateTokenIsTrackable($url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default key and final URL to the given $url
|
||||
$trackableKey = $trackableUrl = $url;
|
||||
|
||||
// Convert URL
|
||||
$urlParts = parse_url($url);
|
||||
|
||||
// We need to ignore not parsable and invalid urls
|
||||
if (false === $urlParts || !$this->isValidUrl($urlParts, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if URL is trackable
|
||||
$tokenizedHost = (!isset($urlParts['host']) && isset($urlParts['path'])) ? $urlParts['path'] : $urlParts['host'];
|
||||
if (preg_match('/^(\{\S+?\})/', $tokenizedHost, $match)) {
|
||||
$token = $match[1];
|
||||
|
||||
// Tokenized hosts that are standalone tokens shouldn't use a scheme since the token value should contain it
|
||||
if ($token === $tokenizedHost && $scheme = (!empty($urlParts['scheme'])) ? $urlParts['scheme'] : false) {
|
||||
// Token has a schema so let's get rid of it before replacing tokens
|
||||
$this->contentReplacements['first_pass'][$scheme.'://'.$tokenizedHost] = $tokenizedHost;
|
||||
unset($urlParts['scheme']);
|
||||
}
|
||||
|
||||
// Validate that the token is something that can be trackable
|
||||
if (!$this->validateTokenIsTrackable($token, $tokenizedHost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not convert contact tokens
|
||||
if (!$this->isContactFieldToken($token)) {
|
||||
$trackableUrl = (!empty($urlParts['query'])) ? $this->contentTokens[$token].'?'.$urlParts['query'] : $this->contentTokens[$token];
|
||||
$trackableKey = $trackableUrl;
|
||||
|
||||
// Replace the URL token with the actual URL
|
||||
$this->contentReplacements['first_pass'][$url] = $trackableUrl;
|
||||
}
|
||||
} else {
|
||||
// Regular URL without a tokenized host
|
||||
$trackableUrl = $this->httpBuildUrl($urlParts);
|
||||
|
||||
if ($this->isInDoNotTrack($trackableUrl)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isInDoNotTrack($trackableUrl)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [$trackableKey, $trackableUrl];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a URL/token is in the do not track list.
|
||||
*/
|
||||
protected function isInDoNotTrack($url): bool
|
||||
{
|
||||
// Ensure it's not in the do not track list
|
||||
foreach ($this->doNotTrack as $notTrackable) {
|
||||
if (preg_match('~'.$notTrackable.'~', $url)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a token is trackable as a URL.
|
||||
*/
|
||||
protected function validateTokenIsTrackable($token, $tokenizedHost = null): bool
|
||||
{
|
||||
// Validate if this token is listed as not to be tracked
|
||||
if ($this->isInDoNotTrack($token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isContactFieldToken($token)) {
|
||||
// Assume it's true as the redirect methods should handle this dynamically
|
||||
return true;
|
||||
}
|
||||
|
||||
$tokenValue = TokenHelper::getValueFromTokens($this->contentTokens, $token);
|
||||
|
||||
// Validate that the token is available
|
||||
if (!$tokenValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($tokenizedHost) {
|
||||
$url = str_ireplace($token, $tokenValue, $tokenizedHost);
|
||||
|
||||
return $this->isValidUrl($url, false);
|
||||
}
|
||||
|
||||
if (!$this->isValidUrl($tokenValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $forceScheme
|
||||
*/
|
||||
protected function isValidUrl($url, $forceScheme = true): bool
|
||||
{
|
||||
$urlParts = (!is_array($url)) ? parse_url($url) : $url;
|
||||
|
||||
// Ensure a applicable URL (rule out URLs as just #)
|
||||
if (!isset($urlParts['host']) && !isset($urlParts['path'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure a valid scheme
|
||||
if (($forceScheme && !isset($urlParts['scheme']))
|
||||
|| (isset($urlParts['scheme'])
|
||||
&& !in_array(
|
||||
$urlParts['scheme'],
|
||||
['http', 'https', 'ftp', 'ftps', 'mailto']
|
||||
))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and extract tokens from the URL as this have to be processed outside of tracking tokens.
|
||||
*
|
||||
* @param $urlParts Array from parse_url
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
protected function extractTokensFromQuery(&$urlParts)
|
||||
{
|
||||
$tokenizedParams = false;
|
||||
|
||||
// Check for a token with a query appended such as {pagelink=1}&key=value
|
||||
if (isset($urlParts['path']) && preg_match('/([https?|ftps?]?\{.*?\})&(.*?)$/', $urlParts['path'], $match)) {
|
||||
$urlParts['path'] = $match[1];
|
||||
if (isset($urlParts['query'])) {
|
||||
// Likely won't happen but append if this exists
|
||||
$urlParts['query'] .= '&'.$match[2];
|
||||
} else {
|
||||
$urlParts['query'] = $match[2];
|
||||
}
|
||||
}
|
||||
|
||||
// Check for tokens in the query
|
||||
if (!empty($urlParts['query'])) {
|
||||
[$tokenizedParams, $untokenizedParams] = $this->parseTokenizedQuery($urlParts['query']);
|
||||
if ($tokenizedParams) {
|
||||
// Rebuild the query without the tokenized query params for now
|
||||
$urlParts['query'] = $this->httpBuildQuery($untokenizedParams);
|
||||
}
|
||||
}
|
||||
|
||||
return $tokenizedParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group query parameters into those that have tokens and those that do not.
|
||||
*
|
||||
* @return array<array<string, mixed>> [$tokenizedParams[], $untokenizedParams[]]
|
||||
*/
|
||||
protected function parseTokenizedQuery($query): array
|
||||
{
|
||||
$tokenizedParams =
|
||||
$untokenizedParams = [];
|
||||
|
||||
// Test to see if there are tokens in the query and if so, extract and append them to the end of the tracked link
|
||||
if (preg_match('/(\{\S+?\})/', $query)) {
|
||||
// Equal signs in tokens will confuse parse_str so they need to be encoded
|
||||
$query = preg_replace('/\{(\S+?)=(\S+?)\}/', '{$1%3D$2}', $query);
|
||||
|
||||
parse_str($query, $queryParts);
|
||||
|
||||
foreach ($queryParts as $key => $value) {
|
||||
if (preg_match('/(\{\S+?\})/', $key) || preg_match('/(\{\S+?\})/', $value)) {
|
||||
$tokenizedParams[$key] = $value;
|
||||
} else {
|
||||
$untokenizedParams[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$tokenizedParams, $untokenizedParams];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Trackable|Redirect>
|
||||
*/
|
||||
protected function getEntitiesFromUrls($trackableUrls, $channel, $channelId)
|
||||
{
|
||||
if (!empty($channel) && !empty($channelId)) {
|
||||
// Track as channel aware
|
||||
return $this->getTrackablesByUrls($trackableUrls, $channel, $channelId);
|
||||
}
|
||||
|
||||
// Simple redirects
|
||||
return $this->getRedirectModel()->getRedirectsByUrls($trackableUrls);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function httpBuildUrl($parts)
|
||||
{
|
||||
if (function_exists('http_build_url')) {
|
||||
return http_build_url($parts);
|
||||
} else {
|
||||
/*
|
||||
* Used if extension is not installed
|
||||
*
|
||||
* http_build_url
|
||||
* Stand alone version of http_build_url (http://php.net/manual/en/function.http-build-url.php)
|
||||
* Based on buggy and inefficient version I found at http://www.mediafire.com/?zjry3tynkg5 by tycoonmaster[at]gmail[dot]com
|
||||
*
|
||||
* @author Chris Nasr (chris[at]fuelforthefire[dot]ca)
|
||||
* @copyright Fuel for the Fire
|
||||
* @package http
|
||||
* @version 0.1
|
||||
* @created 2012-07-26
|
||||
*/
|
||||
|
||||
if (!defined('HTTP_URL_REPLACE')) {
|
||||
// Define constants
|
||||
define('HTTP_URL_REPLACE', 0x0001); // Replace every part of the first URL when there's one of the second URL
|
||||
define('HTTP_URL_JOIN_PATH', 0x0002); // Join relative paths
|
||||
define('HTTP_URL_JOIN_QUERY', 0x0004); // Join query strings
|
||||
define('HTTP_URL_STRIP_USER', 0x0008); // Strip any user authentication information
|
||||
define('HTTP_URL_STRIP_PASS', 0x0010); // Strip any password authentication information
|
||||
define('HTTP_URL_STRIP_PORT', 0x0020); // Strip explicit port numbers
|
||||
define('HTTP_URL_STRIP_PATH', 0x0040); // Strip complete path
|
||||
define('HTTP_URL_STRIP_QUERY', 0x0080); // Strip query string
|
||||
define('HTTP_URL_STRIP_FRAGMENT', 0x0100); // Strip any fragments (#identifier)
|
||||
|
||||
// Combination constants
|
||||
define('HTTP_URL_STRIP_AUTH', HTTP_URL_STRIP_USER | HTTP_URL_STRIP_PASS);
|
||||
define('HTTP_URL_STRIP_ALL', HTTP_URL_STRIP_AUTH | HTTP_URL_STRIP_PORT | HTTP_URL_STRIP_QUERY | HTTP_URL_STRIP_FRAGMENT);
|
||||
}
|
||||
|
||||
$flags = HTTP_URL_REPLACE;
|
||||
$url = [];
|
||||
|
||||
// Scheme and Host are always replaced
|
||||
if (isset($parts['scheme'])) {
|
||||
$url['scheme'] = $parts['scheme'];
|
||||
}
|
||||
if (isset($parts['host'])) {
|
||||
$url['host'] = $parts['host'];
|
||||
}
|
||||
|
||||
// (If applicable) Replace the original URL with it's new parts
|
||||
if (HTTP_URL_REPLACE & $flags) {
|
||||
// Go through each possible key
|
||||
foreach (['user', 'pass', 'port', 'path', 'query', 'fragment'] as $key) {
|
||||
// If it's set in $parts, replace it in $url
|
||||
if (isset($parts[$key])) {
|
||||
$url[$key] = $parts[$key];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Join the original URL path with the new path
|
||||
if (isset($parts['path']) && (HTTP_URL_JOIN_PATH & $flags)) {
|
||||
if (isset($url['path']) && '' != $url['path']) {
|
||||
// If the URL doesn't start with a slash, we need to merge
|
||||
if ('/' != $url['path'][0]) {
|
||||
// If the path ends with a slash, store as is
|
||||
if ('/' == $parts['path'][strlen($parts['path']) - 1]) {
|
||||
$sBasePath = $parts['path'];
|
||||
} // Else trim off the file
|
||||
else {
|
||||
// Get just the base directory
|
||||
$sBasePath = dirname($parts['path']);
|
||||
}
|
||||
|
||||
// If it's empty
|
||||
if ('' == $sBasePath) {
|
||||
$sBasePath = '/';
|
||||
}
|
||||
|
||||
// Add the two together
|
||||
$url['path'] = $sBasePath.$url['path'];
|
||||
|
||||
// Free memory
|
||||
unset($sBasePath);
|
||||
}
|
||||
|
||||
if (str_contains($url['path'], './')) {
|
||||
// Remove any '../' and their directories
|
||||
while (preg_match('/\w+\/\.\.\//', $url['path'])) {
|
||||
$url['path'] = preg_replace('/\w+\/\.\.\//', '', $url['path']);
|
||||
}
|
||||
|
||||
// Remove any './'
|
||||
$url['path'] = str_replace('./', '', $url['path']);
|
||||
}
|
||||
} else {
|
||||
$url['path'] = $parts['path'];
|
||||
}
|
||||
}
|
||||
|
||||
// Join the original query string with the new query string
|
||||
if (isset($parts['query']) && (HTTP_URL_JOIN_QUERY & $flags)) {
|
||||
if (isset($url['query'])) {
|
||||
$url['query'] .= '&'.$parts['query'];
|
||||
} else {
|
||||
$url['query'] = $parts['query'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strips all the applicable sections of the URL
|
||||
if (HTTP_URL_STRIP_USER & $flags) {
|
||||
unset($url['user']);
|
||||
}
|
||||
if (HTTP_URL_STRIP_PASS & $flags) {
|
||||
unset($url['pass']);
|
||||
}
|
||||
if (HTTP_URL_STRIP_PORT & $flags) {
|
||||
unset($url['port']);
|
||||
}
|
||||
if (HTTP_URL_STRIP_PATH & $flags) {
|
||||
unset($url['path']);
|
||||
}
|
||||
if (HTTP_URL_STRIP_QUERY & $flags) {
|
||||
unset($url['query']);
|
||||
}
|
||||
if (HTTP_URL_STRIP_FRAGMENT & $flags) {
|
||||
unset($url['fragment']);
|
||||
}
|
||||
|
||||
// Combine the new elements into a string and return it
|
||||
return
|
||||
((isset($url['scheme'])) ? 'mailto' == $url['scheme'] ? $url['scheme'].':' : $url['scheme'].'://' : '')
|
||||
.((isset($url['user'])) ? $url['user'].((isset($url['pass'])) ? ':'.$url['pass'] : '').'@' : '')
|
||||
.($url['host'] ?? '')
|
||||
.((isset($url['port'])) ? ':'.$url['port'] : '')
|
||||
.($url['path'] ?? '')
|
||||
.((!empty($url['query'])) ? '?'.$url['query'] : '')
|
||||
.((!empty($url['fragment'])) ? '#'.$url['fragment'] : '');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build query string while accounting for tokens that include an equal sign.
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
protected function httpBuildQuery(array $queryParts)
|
||||
{
|
||||
$query = http_build_query($queryParts);
|
||||
|
||||
// http_build_query likely encoded tokens so that has to be fixed so they get replaced
|
||||
$query = preg_replace_callback(
|
||||
'/%7B(\S+?)%7D/i',
|
||||
fn ($matches): string => urldecode($matches[0]),
|
||||
$query
|
||||
);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function isContactFieldToken($token): bool
|
||||
{
|
||||
return str_contains($token, '{contactfield') || str_contains($token, '{leadfield');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, Redirect|Trackable> $trackableTokens
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function parseContent($content, $channel, $channelId, array &$trackableTokens)
|
||||
{
|
||||
// Reset content replacement arrays
|
||||
$this->contentReplacements = [
|
||||
// PHPSTAN reported duplicate keys in this array. I can't determine which is the right one.
|
||||
// I'm leaving the second one to keep current behaviour but leaving the first one commented
|
||||
// out as it may be the one we want.
|
||||
// 'first_pass' => [
|
||||
// // Remove internal attributes
|
||||
// // Editor may convert to HTML4
|
||||
// 'mautic:disable-tracking=""' => '',
|
||||
// // HTML5
|
||||
// 'mautic:disable-tracking' => '',
|
||||
// ],
|
||||
'first_pass' => [],
|
||||
'second_pass' => [],
|
||||
];
|
||||
|
||||
$trackableUrls = $this->extractTrackablesFromContent($content);
|
||||
$contentType = (preg_match('/<(.*?) href/i', $content)) ? 'html' : 'text';
|
||||
if (count($trackableUrls)) {
|
||||
// Create Trackable/Redirect entities for the URLs
|
||||
$entities = $this->getEntitiesFromUrls($trackableUrls, $channel, $channelId);
|
||||
unset($trackableUrls);
|
||||
|
||||
// Get a list of url => token to return to calling method and also to be used to
|
||||
// replace the urls in the content with tokens
|
||||
$trackableTokens = array_merge(
|
||||
$trackableTokens,
|
||||
$this->createTrackingTokens($entities)
|
||||
);
|
||||
|
||||
unset($entities);
|
||||
|
||||
// Replace URLs in content with tokens
|
||||
$content = $this->prepareContentWithTrackableTokens($content, $contentType);
|
||||
} elseif (!empty($this->contentReplacements['first_pass'])) {
|
||||
// Replace URLs in content with tokens
|
||||
$content = $this->prepareContentWithTrackableTokens($content, $contentType);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getContactFieldUrlTokens()
|
||||
{
|
||||
if (null !== $this->contactFieldUrlTokens) {
|
||||
return $this->contactFieldUrlTokens;
|
||||
}
|
||||
|
||||
$this->contactFieldUrlTokens = [];
|
||||
|
||||
$fieldEntities = $this->leadFieldRepository->getFieldsByType('url');
|
||||
foreach ($fieldEntities as $field) {
|
||||
$this->contactFieldUrlTokens[] = $field->getAlias();
|
||||
}
|
||||
|
||||
$this->leadFieldRepository->detachEntities($fieldEntities);
|
||||
|
||||
return $this->contactFieldUrlTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DOMNodeList<\DOMNode> $links
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function extractTrackables(\DOMNodeList $links): array
|
||||
{
|
||||
$trackableUrls = [];
|
||||
/** @var \DOMElement $link */
|
||||
foreach ($links as $link) {
|
||||
$url = $link->getAttribute('href');
|
||||
|
||||
if ('' === $url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for a do not track
|
||||
if ($link->hasAttribute('mautic:disable-tracking')) {
|
||||
$this->doNotTrack[$url] = $url;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($preparedUrl = $this->prepareUrlForTracking($url)) {
|
||||
[$urlKey, $urlValue] = $preparedUrl;
|
||||
$trackableUrls[$urlKey] = $urlValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $trackableUrls;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PageBundle\Model;
|
||||
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\LeadBundle\Tracker\ContactTracker;
|
||||
use Mautic\PageBundle\Entity\Page;
|
||||
use Mautic\PageBundle\Entity\Redirect;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class Tracking404Model
|
||||
{
|
||||
public function __construct(
|
||||
private CoreParametersHelper $coreParametersHelper,
|
||||
private ContactTracker $contactTracker,
|
||||
private PageModel $pageModel,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Page|Redirect $entity
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function hitPage($entity, Request $request): void
|
||||
{
|
||||
$this->pageModel->hitPage($entity, $request, 404);
|
||||
}
|
||||
|
||||
public function isTrackable(): bool
|
||||
{
|
||||
if (!$this->coreParametersHelper->get('do_not_track_404_anonymous')) {
|
||||
return true;
|
||||
}
|
||||
// already tracked and identified contact
|
||||
if ($lead = $this->contactTracker->getContactByTrackedDevice()) {
|
||||
if (!$lead->isAnonymous()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PageBundle\Model;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\IpLookupHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Model\FormModel;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Tracker\ContactTracker;
|
||||
use Mautic\PageBundle\Entity\VideoHit;
|
||||
use Mautic\PageBundle\Entity\VideoHitRepository;
|
||||
use Mautic\PageBundle\Event\VideoHitEvent;
|
||||
use Mautic\PageBundle\PageEvents;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
/**
|
||||
* @extends FormModel<VideoHit>
|
||||
*/
|
||||
class VideoModel extends FormModel
|
||||
{
|
||||
public function __construct(
|
||||
protected IpLookupHelper $ipLookupHelper,
|
||||
protected ContactTracker $contactTracker,
|
||||
EntityManager $em,
|
||||
CorePermissions $security,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
UrlGeneratorInterface $router,
|
||||
Translator $translator,
|
||||
UserHelper $userHelper,
|
||||
LoggerInterface $mauticLogger,
|
||||
CoreParametersHelper $coreParametersHelper,
|
||||
) {
|
||||
parent::__construct($em, $security, $dispatcher, $router, $translator, $userHelper, $mauticLogger, $coreParametersHelper);
|
||||
}
|
||||
|
||||
public function getHitRepository(): VideoHitRepository
|
||||
{
|
||||
return $this->em->getRepository(VideoHit::class);
|
||||
}
|
||||
|
||||
public function getPermissionBase(): string
|
||||
{
|
||||
return 'page:pages';
|
||||
}
|
||||
|
||||
public function getNameGetter(): string
|
||||
{
|
||||
return 'getTitle';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $guid
|
||||
*
|
||||
* @return VideoHit
|
||||
*/
|
||||
public function getHitForLeadByGuid(Lead $lead, $guid)
|
||||
{
|
||||
return $this->getHitRepository()->getHitForLeadByGuid($lead, $guid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param string $code
|
||||
*
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function hitVideo($request, $code = '200'): void
|
||||
{
|
||||
// don't skew results with in-house hits
|
||||
if (!$this->security->isAnonymous()) {
|
||||
// return;
|
||||
}
|
||||
|
||||
$lead = $this->contactTracker->getContact();
|
||||
$guid = $request->get('guid');
|
||||
|
||||
$hit = ($lead) ? $this->getHitForLeadByGuid($lead, $guid) : new VideoHit();
|
||||
|
||||
$hit->setGuid($guid);
|
||||
$hit->setDateHit(new \DateTime());
|
||||
|
||||
$hit->setDuration($request->get('duration'));
|
||||
$hit->setUrl($request->get('url'));
|
||||
$hit->setTimeWatched($request->get('total_watched'));
|
||||
|
||||
// check for existing IP
|
||||
$ipAddress = $this->ipLookupHelper->getIpAddress();
|
||||
$hit->setIpAddress($ipAddress);
|
||||
|
||||
// Store query array
|
||||
$query = $request->query->all();
|
||||
unset($query['d']);
|
||||
$hit->setQuery($query);
|
||||
|
||||
if ($lead) {
|
||||
$hit->setLead($lead);
|
||||
}
|
||||
|
||||
// glean info from the IP address
|
||||
if ($details = $ipAddress->getIpDetails()) {
|
||||
$hit->setCountry($details['country']);
|
||||
$hit->setRegion($details['region']);
|
||||
$hit->setCity($details['city']);
|
||||
$hit->setIsp($details['isp']);
|
||||
$hit->setOrganization($details['organization']);
|
||||
}
|
||||
|
||||
$hit->setCode($code);
|
||||
if (!$hit->getReferer()) {
|
||||
$hit->setReferer($request->server->get('HTTP_REFERER'));
|
||||
}
|
||||
|
||||
$hit->setUserAgent($request->server->get('HTTP_USER_AGENT'));
|
||||
$hit->setRemoteHost($request->server->get('REMOTE_HOST'));
|
||||
|
||||
// get a list of the languages the user prefers
|
||||
$browserLanguages = $request->server->get('HTTP_ACCEPT_LANGUAGE');
|
||||
if (!empty($browserLanguages)) {
|
||||
$languages = explode(',', $browserLanguages);
|
||||
foreach ($languages as $k => $l) {
|
||||
if (($pos = strpos(';q=', $l)) !== false) {
|
||||
// remove weights
|
||||
$languages[$k] = substr($l, 0, $pos);
|
||||
}
|
||||
}
|
||||
$hit->setBrowserLanguages($languages);
|
||||
}
|
||||
|
||||
// Wrap in a try/catch to prevent deadlock errors on busy servers
|
||||
try {
|
||||
$this->em->persist($hit);
|
||||
$this->em->flush();
|
||||
} catch (\Exception $exception) {
|
||||
if (MAUTIC_ENV === 'dev') {
|
||||
throw $exception;
|
||||
} else {
|
||||
$this->logger->error(
|
||||
$exception->getMessage(),
|
||||
['exception' => $exception]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->dispatcher->hasListeners(PageEvents::VIDEO_ON_HIT)) {
|
||||
$event = new VideoHitEvent($hit, $request, $code);
|
||||
$this->dispatcher->dispatch($event, PageEvents::VIDEO_ON_HIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user