![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/mautic.corals.io/app/bundles/AssetBundle/Model/ |
<?php namespace Mautic\AssetBundle\Model; use Doctrine\ORM\EntityManager; use Doctrine\ORM\PersistentCollection; use Mautic\AssetBundle\AssetEvents; use Mautic\AssetBundle\Entity\Asset; use Mautic\AssetBundle\Entity\Download; use Mautic\AssetBundle\Event\AssetEvent; use Mautic\AssetBundle\Event\AssetLoadEvent; use Mautic\AssetBundle\Form\Type\AssetType; use Mautic\CategoryBundle\Model\CategoryModel; use Mautic\CoreBundle\Helper\Chart\ChartQuery; use Mautic\CoreBundle\Helper\Chart\LineChart; use Mautic\CoreBundle\Helper\Chart\PieChart; use Mautic\CoreBundle\Helper\CoreParametersHelper; use Mautic\CoreBundle\Helper\FileHelper; 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\EmailBundle\Entity\Email; use Mautic\LeadBundle\Entity\Lead; use Mautic\LeadBundle\Model\LeadModel; use Mautic\LeadBundle\Tracker\ContactTracker; use Mautic\LeadBundle\Tracker\Factory\DeviceDetectorFactory\DeviceDetectorFactoryInterface; use Mautic\LeadBundle\Tracker\Service\DeviceCreatorService\DeviceCreatorServiceInterface; use Mautic\LeadBundle\Tracker\Service\DeviceTrackingService\DeviceTrackingServiceInterface; use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Contracts\EventDispatcher\Event; /** * @extends FormModel<Asset> */ class AssetModel extends FormModel { /** * @var int */ protected $maxAssetSize; public function __construct( protected LeadModel $leadModel, protected CategoryModel $categoryModel, private RequestStack $requestStack, protected IpLookupHelper $ipLookupHelper, private DeviceCreatorServiceInterface $deviceCreatorService, private DeviceDetectorFactoryInterface $deviceDetectorFactory, private DeviceTrackingServiceInterface $deviceTrackingService, private ContactTracker $contactTracker, EntityManager $em, CorePermissions $security, EventDispatcherInterface $dispatcher, UrlGeneratorInterface $router, Translator $translator, UserHelper $userHelper, LoggerInterface $logger, CoreParametersHelper $coreParametersHelper ) { $this->maxAssetSize = $coreParametersHelper->get('max_size'); parent::__construct($em, $security, $dispatcher, $router, $translator, $userHelper, $logger, $coreParametersHelper); } public function saveEntity($entity, $unlock = true): void { if (empty($this->inConversion)) { $alias = $entity->getAlias(); if (empty($alias)) { $alias = $entity->getTitle(); } $alias = $this->cleanAlias($alias, '', 0, '-'); // make sure alias is not already taken $repo = $this->getRepository(); $testAlias = $alias; $count = $repo->checkUniqueAlias($testAlias, $entity); $aliasTag = $count; while ($count) { $testAlias = $alias.$aliasTag; $count = $repo->checkUniqueAlias($testAlias, $entity); ++$aliasTag; } if ($testAlias != $alias) { $alias = $testAlias; } $entity->setAlias($alias); } if (!$entity->isNew()) { // increase the revision $revision = $entity->getRevision(); ++$revision; $entity->setRevision($revision); } parent::saveEntity($entity, $unlock); } /** * @param string $code * @param array $systemEntry * * @throws \Doctrine\ORM\ORMException * @throws \Exception */ public function trackDownload($asset, $request = null, $code = '200', $systemEntry = []): void { // Don't skew results with in-house downloads if (empty($systemEntry) && !$this->security->isAnonymous()) { return; } if (null == $request) { $request = $this->requestStack->getCurrentRequest(); } $download = new Download(); $download->setDateDownload(new \DateTime()); $download->setUtmCampaign($request->get('utm_campaign')); $download->setUtmContent($request->get('utm_content')); $download->setUtmMedium($request->get('utm_medium')); $download->setUtmSource($request->get('utm_source')); $download->setUtmTerm($request->get('utm_term')); // Download triggered by lead if (empty($systemEntry)) { // check for any clickthrough info $clickthrough = $request->get('ct', false); if (!empty($clickthrough)) { $clickthrough = $this->decodeArrayFromUrl($clickthrough); if (!empty($clickthrough['lead'])) { $lead = $this->leadModel->getEntity($clickthrough['lead']); if (null !== $lead) { $wasTrackedAlready = $this->deviceTrackingService->isTracked(); $deviceDetector = $this->deviceDetectorFactory->create($request->server->get('HTTP_USER_AGENT')); $deviceDetector->parse(); $currentDevice = $this->deviceCreatorService->getCurrentFromDetector($deviceDetector, $lead); $trackedDevice = $this->deviceTrackingService->trackCurrentDevice($currentDevice, false); $trackingId = $trackedDevice->getTrackingId(); $trackingNewlyGenerated = !$wasTrackedAlready; $leadClickthrough = true; $this->contactTracker->setTrackedContact($lead); } } if (!empty($clickthrough['channel'])) { if (1 === count($clickthrough['channel'])) { $channelId = reset($clickthrough['channel']); $channel = key($clickthrough['channel']); } else { $channel = $clickthrough['channel'][0]; $channelId = (int) $clickthrough['channel'][1]; } $download->setSource($channel); $download->setSourceId($channelId); } elseif (!empty($clickthrough['source'])) { $download->setSource($clickthrough['source'][0]); $download->setSourceId($clickthrough['source'][1]); } if (!empty($clickthrough['email'])) { $emailRepo = $this->em->getRepository(Email::class); if ($emailEntity = $emailRepo->getEntity($clickthrough['email'])) { $download->setEmail($emailEntity); } } } if (empty($leadClickthrough)) { $wasTrackedAlready = $this->deviceTrackingService->isTracked(); $lead = $this->contactTracker->getContact(); $trackedDevice = $this->deviceTrackingService->getTrackedDevice(); $trackingId = null; $trackingNewlyGenerated = false; if (null !== $trackedDevice) { $trackingId = $trackedDevice->getTrackingId(); $trackingNewlyGenerated = !$wasTrackedAlready; } } $download->setLead($lead); } else { $trackingId = ''; if (isset($systemEntry['lead'])) { $lead = $systemEntry['lead']; if (!$lead instanceof Lead) { $leadId = is_array($lead) ? $lead['id'] : $lead; $lead = $this->em->getReference(Lead::class, $leadId); } $download->setLead($lead); } if (!empty($systemEntry['source'])) { $download->setSource($systemEntry['source'][0]); $download->setSourceId($systemEntry['source'][1]); } if (isset($systemEntry['email'])) { $email = $systemEntry['email']; if (!$email instanceof Email) { $emailId = is_array($email) ? $email['id'] : $email; $email = $this->em->getReference(Email::class, $emailId); } $download->setEmail($email); } if (isset($systemEntry['tracking_id'])) { $trackingId = $systemEntry['tracking_id']; $trackingNewlyGenerated = false; } elseif ($this->security->isAnonymous() && !defined('IN_MAUTIC_CONSOLE')) { // If the session is anonymous and not triggered via CLI, assume the lead did something to trigger the // system forced download such as an email $deviceWasTracked = $this->deviceTrackingService->isTracked(); $deviceDetector = $this->deviceDetectorFactory->create($request->server->get('HTTP_USER_AGENT')); $deviceDetector->parse(); $currentDevice = $this->deviceCreatorService->getCurrentFromDetector($deviceDetector, $lead); $trackedDevice = $this->deviceTrackingService->trackCurrentDevice($currentDevice, false); $trackingId = $trackedDevice->getTrackingId(); $trackingNewlyGenerated = !$deviceWasTracked; } } $isUnique = true; if (!empty($trackingNewlyGenerated)) { // Cookie was just generated so this is definitely a unique download $isUnique = $trackingNewlyGenerated; } elseif (!empty($trackingId)) { // Determine if this is a unique download $isUnique = $this->getDownloadRepository()->isUniqueDownload($asset->getId(), $trackingId); } $download->setTrackingId($trackingId); if (!empty($asset) && empty($systemEntry)) { $download->setAsset($asset); $this->getRepository()->upDownloadCount($asset->getId(), 1, $isUnique); } // check for existing IP $ipAddress = $this->ipLookupHelper->getIpAddress(); $download->setCode($code); $download->setIpAddress($ipAddress); if (null !== $request) { $download->setReferer($request->server->get('HTTP_REFERER')); } // Dispatch event if ($this->dispatcher->hasListeners(AssetEvents::ASSET_ON_LOAD)) { $event = new AssetLoadEvent($download, $isUnique); $this->dispatcher->dispatch($event, AssetEvents::ASSET_ON_LOAD); } // Wrap in a try/catch to prevent deadlock errors on busy servers try { $this->em->persist($download); $this->em->flush(); } catch (\Exception $e) { if (MAUTIC_ENV === 'dev') { throw $e; } else { error_log($e); } } $this->em->detach($download); } /** * Increase the download count. * * @param int $increaseBy * @param bool|false $unique */ public function upDownloadCount($asset, $increaseBy = 1, $unique = false): void { $id = ($asset instanceof Asset) ? $asset->getId() : (int) $asset; $this->getRepository()->upDownloadCount($id, $increaseBy, $unique); } /** * @return \Mautic\AssetBundle\Entity\AssetRepository */ public function getRepository() { return $this->em->getRepository(Asset::class); } /** * @return \Mautic\AssetBundle\Entity\DownloadRepository */ public function getDownloadRepository() { return $this->em->getRepository(Download::class); } public function getPermissionBase(): string { return 'asset:assets'; } public function getNameGetter(): string { return 'getTitle'; } /** * @throws NotFoundHttpException */ public function createForm($entity, FormFactoryInterface $formFactory, $action = null, $options = []): \Symfony\Component\Form\FormInterface { if (!$entity instanceof Asset) { throw new MethodNotAllowedHttpException(['Asset']); } if (!empty($action)) { $options['action'] = $action; } return $formFactory->create(AssetType::class, $entity, $options); } /** * Get a specific entity or generate a new one if id is empty. */ public function getEntity($id = null): ?Asset { if (null === $id) { $entity = new Asset(); } else { $entity = parent::getEntity($id); } return $entity; } /** * @throws MethodNotAllowedHttpException */ protected function dispatchEvent($action, &$entity, $isNew = false, Event $event = null): ?Event { if (!$entity instanceof Asset) { throw new MethodNotAllowedHttpException(['Asset']); } switch ($action) { case 'pre_save': $name = AssetEvents::ASSET_PRE_SAVE; break; case 'post_save': $name = AssetEvents::ASSET_POST_SAVE; break; case 'pre_delete': $name = AssetEvents::ASSET_PRE_DELETE; break; case 'post_delete': $name = AssetEvents::ASSET_POST_DELETE; break; default: return null; } if ($this->dispatcher->hasListeners($name)) { if (empty($event)) { $event = new AssetEvent($entity, $isNew); $event->setEntityManager($this->em); } $this->dispatcher->dispatch($event, $name); return $event; } else { return null; } } /** * Get list of entities for autopopulate fields. * * @return array */ public function getLookupResults($type, $filter = '', $limit = 10) { $results = []; switch ($type) { case 'asset': $viewOther = $this->security->isGranted('asset:assets:viewother'); $request = $this->requestStack->getCurrentRequest(); $repo = $this->getRepository(); $repo->setCurrentUser($this->userHelper->getUser()); // During the form submit & edit, make sure that the data is checked against available assets if ('mautic_segment_action' === $request->get('_route') && (Request::METHOD_POST === $request->getMethod() || 'edit' === $request->get('objectAction')) ) { $limit = 0; } $results = $repo->getAssetList($filter, $limit, 0, $viewOther); break; case 'category': $results = $this->categoryModel->getRepository()->getCategoryList($filter, $limit, 0); break; } return $results; } /** * Generate url for an asset. * * @param Asset $entity * @param bool $absolute * @param array $clickthrough * * @return string */ public function generateUrl($entity, $absolute = true, $clickthrough = []) { $assetSlug = $entity->getId().':'.$entity->getAlias(); $slugs = [ 'slug' => $assetSlug, ]; return $this->buildUrl('mautic_asset_download', $slugs, $absolute, $clickthrough); } /** * Determine the max upload size based on PHP restrictions and config. * * @param string $unit If '', determine the best unit based on the number * @param bool|false $humanReadable Return as a human readable filesize * * @return float */ public function getMaxUploadSize($unit = 'M', $humanReadable = false) { $maxAssetSize = $this->maxAssetSize; $maxAssetSize = (-1 == $maxAssetSize || 0 === $maxAssetSize) ? PHP_INT_MAX : FileHelper::convertMegabytesToBytes($maxAssetSize); $maxPostSize = Asset::getIniValue('post_max_size'); $maxUploadSize = Asset::getIniValue('upload_max_filesize'); $memoryLimit = Asset::getIniValue('memory_limit'); $maxAllowed = min(array_filter([$maxAssetSize, $maxPostSize, $maxUploadSize, $memoryLimit])); if ($humanReadable) { $number = Asset::convertBytesToHumanReadable($maxAllowed); } else { [$number, $unit] = Asset::convertBytesToUnit($maxAllowed, $unit); } return $number; } /** * @return int|string */ public function getTotalFilesize($assets) { $firstAsset = is_array($assets) ? reset($assets) : false; if ($assets instanceof PersistentCollection || is_object($firstAsset)) { $assetIds = []; foreach ($assets as $asset) { $assetIds[] = $asset->getId(); } $assets = $assetIds; } if (!is_array($assets)) { $assets = [$assets]; } if (empty($assets)) { return 0; } $repo = $this->getRepository(); $size = $repo->getAssetSize($assets); if ($size) { $size = Asset::convertBytesToHumanReadable($size); } return $size; } /** * Get line chart data of downloads. * * @param string|null $unit {@link php.net/manual/en/function.date.php#refsect1-function.date-parameters} * @param string $dateFormat * @param array $filter * @param bool $canViewOthers */ public function getDownloadsLineChartData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat = null, $filter = [], $canViewOthers = true): array { $chart = new LineChart($unit, $dateFrom, $dateTo, $dateFormat); $query = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo); $q = $query->prepareTimeDataQuery('asset_downloads', 'date_download', $filter); if (!$canViewOthers) { $q->join('t', MAUTIC_TABLE_PREFIX.'assets', 'a', 'a.id = t.asset_id') ->andWhere('a.created_by = :userId') ->setParameter('userId', $this->userHelper->getUser()->getId()); } $data = $query->loadAndBuildTimeData($q); $chart->setDataset($this->translator->trans('mautic.asset.downloadcount'), $data); return $chart->render(); } /** * Get pie chart data of unique vs repetitive downloads. * Repetitive in this case mean if a lead downloaded any of the assets more than once. * * @param string $dateFrom * @param string $dateTo * @param array $filters * @param bool $canViewOthers */ public function getUniqueVsRepetitivePieChartData($dateFrom, $dateTo, $filters = [], $canViewOthers = true): array { $chart = new PieChart(); $query = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo); $allQ = $query->getCountQuery('asset_downloads', 'id', 'date_download', $filters); $uniqueQ = $query->getCountQuery('asset_downloads', 'lead_id', 'date_download', $filters, ['getUnique' => true]); if (!$canViewOthers) { $allQ->join('t', MAUTIC_TABLE_PREFIX.'assets', 'a', 'a.id = t.asset_id') ->andWhere('a.created_by = :userId') ->setParameter('userId', $this->userHelper->getUser()->getId()); $uniqueQ->join('t', MAUTIC_TABLE_PREFIX.'assets', 'a', 'a.id = t.asset_id') ->andWhere('a.created_by = :userId') ->setParameter('userId', $this->userHelper->getUser()->getId()); } $all = $query->fetchCount($allQ); $unique = $query->fetchCount($uniqueQ); $repetitive = $all - $unique; $chart->setDataset($this->translator->trans('mautic.asset.unique'), $unique); $chart->setDataset($this->translator->trans('mautic.asset.repetitive'), $repetitive); return $chart->render(); } /** * Get a list of popular (by downloads) assets. * * @param int $limit * @param string $dateFrom * @param string $dateTo * @param array $filters * @param bool $canViewOthers */ public function getPopularAssets($limit = 10, $dateFrom = null, $dateTo = null, $filters = [], $canViewOthers = true): array { $q = $this->em->getConnection()->createQueryBuilder(); $q->select('COUNT(DISTINCT t.id) AS download_count, a.id, a.title') ->from(MAUTIC_TABLE_PREFIX.'asset_downloads', 't') ->join('t', MAUTIC_TABLE_PREFIX.'assets', 'a', 'a.id = t.asset_id') ->orderBy('download_count', 'DESC') ->groupBy('a.id') ->setMaxResults($limit); if (!$canViewOthers) { $q->andWhere('a.created_by = :userId') ->setParameter('userId', $this->userHelper->getUser()->getId()); } $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo); $chartQuery->applyFilters($q, $filters); $chartQuery->applyDateFilters($q, 'date_download'); return $q->execute()->fetchAllAssociative(); } /** * Get a list of assets in a date range. * * @param int $limit * @param array $filters * @param array $options * * @return array */ public function getAssetList($limit = 10, \DateTime $dateFrom = null, \DateTime $dateTo = null, $filters = [], $options = []) { $q = $this->em->getConnection()->createQueryBuilder(); $q->select('t.id, t.title as name, t.date_added, t.date_modified') ->from(MAUTIC_TABLE_PREFIX.'assets', 't') ->setMaxResults($limit); if (!empty($options['canViewOthers'])) { $q->andWhere('t.created_by = :userId') ->setParameter('userId', $this->userHelper->getUser()->getId()); } $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo); $chartQuery->applyFilters($q, $filters); $chartQuery->applyDateFilters($q, 'date_added'); return $q->execute()->fetchAllAssociative(); } }