Initial commit: CloudOps infrastructure platform

This commit is contained in:
root
2026-04-09 19:58:57 +02:00
commit 1166a52f26
7762 changed files with 839452 additions and 0 deletions

View File

@@ -0,0 +1,237 @@
<?php
namespace Mautic\AssetBundle\Entity;
use Doctrine\Common\Collections\Order;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Mautic\CoreBundle\Entity\CommonRepository;
use Mautic\ProjectBundle\Entity\ProjectRepositoryTrait;
/**
* @extends CommonRepository<Asset>
*/
class AssetRepository extends CommonRepository
{
use ProjectRepositoryTrait;
/**
* Get a list of entities.
*
* @return Paginator
*/
public function getEntities(array $args = [])
{
$q = $this
->createQueryBuilder('a')
->select('a')
->leftJoin('a.category', 'c');
$args['qb'] = $q;
return parent::getEntities($args);
}
/**
* @param string $search
* @param int $limit
* @param int $start
* @param bool|false $viewOther
*
* @return array
*/
public function getAssetList($search = '', $limit = 10, $start = 0, $viewOther = false)
{
$q = $this->createQueryBuilder('a');
$q->select('partial a.{id, title, path, alias, language}');
if (!empty($search)) {
$q->andWhere($q->expr()->like('a.title', ':search'))
->setParameter('search', "%{$search}%");
}
if (!$viewOther) {
$q->andWhere($q->expr()->eq('a.createdBy', ':id'))
->setParameter('id', $this->currentUser->getId());
}
$q->orderBy('a.title');
if (!empty($limit)) {
$q->setFirstResult($start)
->setMaxResults($limit);
}
return $q->getQuery()->getArrayResult();
}
/**
* @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q
*/
protected function addCatchAllWhereClause($q, $filter): array
{
return $this->addStandardCatchAllWhereClause($q, $filter, [
'a.title',
'a.alias',
]);
}
/**
* @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q
*/
protected function addSearchCommandWhereClause($q, $filter): array
{
[$expr, $parameters] = $this->addStandardSearchCommandWhereClause($q, $filter);
if ($expr) {
return [$expr, $parameters];
}
$command = $field = $filter->command;
$unique = $this->generateRandomParameterName();
$returnParameter = false; // returning a parameter that is not used will lead to a Doctrine error
switch ($command) {
case $this->translator->trans('mautic.asset.asset.searchcommand.isexpired'):
case $this->translator->trans('mautic.asset.asset.searchcommand.isexpired', [], null, 'en_US'):
$expr = sprintf(
"(a.isPublished = :%1\$s AND a.publishDown IS NOT NULL AND a.publishDown <> '' AND a.publishDown < CURRENT_TIMESTAMP())",
$unique
);
$forceParameters = [$unique => true];
break;
case $this->translator->trans('mautic.asset.asset.searchcommand.ispending'):
case $this->translator->trans('mautic.asset.asset.searchcommand.ispending', [], null, 'en_US'):
$expr = sprintf(
"(a.isPublished = :%1\$s AND a.publishUp IS NOT NULL AND a.publishUp <> '' AND a.publishUp > CURRENT_TIMESTAMP())",
$unique
);
$forceParameters = [$unique => true];
break;
case $this->translator->trans('mautic.asset.asset.searchcommand.lang'):
$langUnique = $this->generateRandomParameterName();
$langValue = $filter->string.'_%';
$forceParameters = [
$langUnique => $langValue,
$unique => $filter->string,
];
$expr = '('.$q->expr()->eq('a.language', ":$unique").' OR '.$q->expr()->like('a.language', ":$langUnique").')';
$returnParameter = true;
break;
case $this->translator->trans('mautic.project.searchcommand.name'):
case $this->translator->trans('mautic.project.searchcommand.name', [], null, 'en_US'):
return $this->handleProjectFilter(
$this->_em->getConnection()->createQueryBuilder(),
'asset_id',
'asset_projects_xref',
$this->getTableAlias(),
$filter->string,
$filter->not
);
}
if ($expr && $filter->not) {
$expr = $q->expr()->not($expr);
}
if (!empty($forceParameters)) {
$parameters = $forceParameters;
} elseif (!$returnParameter) {
$parameters = [];
} else {
$string = ($filter->strict) ? $filter->string : "%{$filter->string}%";
$parameters = ["$unique" => $string];
}
return [$expr, $parameters];
}
/**
* @return string[]
*/
public function getSearchCommands(): array
{
$commands = [
'mautic.core.searchcommand.ispublished',
'mautic.core.searchcommand.isunpublished',
'mautic.core.searchcommand.isuncategorized',
'mautic.core.searchcommand.ismine',
'mautic.asset.asset.searchcommand.isexpired',
'mautic.asset.asset.searchcommand.ispending',
'mautic.core.searchcommand.category',
'mautic.asset.asset.searchcommand.lang',
'mautic.project.searchcommand.name',
];
return array_merge($commands, parent::getSearchCommands());
}
/**
* @return array<array<string>>
*/
protected function getDefaultOrder(): array
{
return [
['a.title', 'ASC'],
];
}
public function getTableAlias(): string
{
return 'a';
}
/**
* Gets the sum size of assets.
*/
public function getAssetSize(array $assets): int
{
$q = $this->_em->getConnection()->createQueryBuilder();
$q->select('sum(a.size) as total_size')
->from(MAUTIC_TABLE_PREFIX.'assets', 'a')
->where('a.id IN (:assetIds)')
->setParameter('assetIds', $assets, \Doctrine\DBAL\ArrayParameterType::INTEGER);
$result = $q->executeQuery()->fetchAllAssociative();
return (int) $result[0]['total_size'];
}
/**
* @param int $increaseBy
* @param bool|false $unique
*/
public function upDownloadCount($id, $increaseBy = 1, $unique = false): void
{
$q = $this->_em->getConnection()->createQueryBuilder();
$q->update(MAUTIC_TABLE_PREFIX.'assets')
->set('download_count', 'download_count + '.(int) $increaseBy)
->where('id = '.(int) $id);
if ($unique) {
$q->set('unique_download_count', 'unique_download_count + '.(int) $increaseBy);
}
$q->executeStatement();
}
/**
* @param int $categoryId
*
* @return Asset
*
* @throws NoResultException
* @throws NonUniqueResultException
*/
public function getLatestAssetForCategory($categoryId)
{
$q = $this->createQueryBuilder($this->getTableAlias());
$q->where($this->getTableAlias().'.category = :categoryId');
$q->andWhere($this->getTableAlias().'.isPublished = TRUE');
$q->setParameter('categoryId', $categoryId);
$q->orderBy($this->getTableAlias().'.dateAdded', Order::Descending->value);
$q->setMaxResults(1);
return $q->getQuery()->getSingleResult();
}
}

View File

@@ -0,0 +1,425 @@
<?php
namespace Mautic\AssetBundle\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
use Mautic\CoreBundle\Entity\IpAddress;
use Mautic\EmailBundle\Entity\Email;
use Mautic\LeadBundle\Entity\Lead;
use Symfony\Component\Serializer\Attribute\Groups;
#[ApiResource(
operations: [
new GetCollection(security: "is_granted('asset:assets:viewown')"),
new Get(security: "is_granted('asset:assets:viewown')"),
],
normalizationContext: [
'groups' => ['download:read'],
'swagger_definition_name' => 'Read',
'api_included' => ['asset', 'ipaddress', 'email'],
],
denormalizationContext: [
'groups' => ['download:write'],
'swagger_definition_name' => 'Write',
]
)]
class Download
{
public const TABLE_NAME = 'asset_downloads';
/**
* @var string
*/
#[Groups(['download:read'])]
private $id;
/**
* @var \DateTimeInterface
*/
#[Groups(['download:read', 'download:write'])]
private $dateDownload;
/**
* @var Asset|null
*/
#[Groups(['download:read', 'download:write'])]
private $asset;
/**
* @var IpAddress|null
*/
#[Groups(['download:read', 'download:write'])]
private $ipAddress;
#[Groups(['download:read', 'download:write'])]
private ?Lead $lead;
/**
* @var int
*/
#[Groups(['download:read', 'download:write'])]
private $code;
/**
* @var string|null
*/
#[Groups(['download:read', 'download:write'])]
private $referer;
/**
* @var string
*/
#[Groups(['download:read', 'download:write'])]
private $trackingId;
/**
* @var string|null
*/
#[Groups(['download:read', 'download:write'])]
private $source;
/**
* @var int|null
*/
#[Groups(['download:read', 'download:write'])]
private $sourceId;
/**
* @var Email|null
*/
#[Groups(['download:read', 'download:write'])]
private $email;
private ?string $utmCampaign = null;
private ?string $utmContent = null;
private ?string $utmMedium = null;
private ?string $utmSource = null;
private ?string $utmTerm = null;
public static function loadMetadata(ORM\ClassMetadata $metadata): void
{
$builder = new ClassMetadataBuilder($metadata);
$builder->setTable(self::TABLE_NAME)
->setCustomRepositoryClass(DownloadRepository::class)
->addIndex(['tracking_id'], 'download_tracking_search')
->addIndex(['source', 'source_id'], 'download_source_search')
->addIndex(['date_download'], 'asset_date_download');
$builder->addBigIntIdField();
$builder->createField('dateDownload', 'datetime')
->columnName('date_download')
->build();
$builder->createManyToOne('asset', 'Asset')
->addJoinColumn('asset_id', 'id', true, false, 'CASCADE')
->build();
$builder->addIpAddress(true);
$builder->addLead(true, 'SET NULL');
$builder->addField('code', 'integer');
$builder->createField('referer', 'text')
->nullable()
->build();
$builder->createField('trackingId', 'string')
->columnName('tracking_id')
->build();
$builder->createField('source', 'string')
->nullable()
->build();
$builder->createField('sourceId', 'integer')
->columnName('source_id')
->nullable()
->build();
$builder->createManyToOne('email', Email::class)
->addJoinColumn('email_id', 'id', true, false, 'SET NULL')
->build();
$builder->createField('utmCampaign', Types::STRING)
->columnName('utm_campaign')
->nullable()
->build();
$builder->createField('utmContent', Types::STRING)
->columnName('utm_content')
->nullable()
->build();
$builder->createField('utmMedium', Types::STRING)
->columnName('utm_medium')
->nullable()
->build();
$builder->createField('utmSource', Types::STRING)
->columnName('utm_source')
->nullable()
->build();
$builder->createField('utmTerm', Types::STRING)
->columnName('utm_term')
->nullable()
->build();
}
public function getId(): int
{
return (int) $this->id;
}
/**
* @param \DateTime $dateDownload
*
* @return Download
*/
public function setDateDownload($dateDownload)
{
$this->dateDownload = $dateDownload;
return $this;
}
/**
* @return \DateTimeInterface
*/
public function getDateDownload()
{
return $this->dateDownload;
}
/**
* @param int $code
*
* @return Download
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* @return int
*/
public function getCode()
{
return $this->code;
}
/**
* @param string $referer
*
* @return Download
*/
public function setReferer($referer)
{
$this->referer = $referer;
return $this;
}
/**
* @return string
*/
public function getReferer()
{
return $this->referer;
}
/**
* @return Download
*/
public function setAsset(?Asset $asset = null)
{
$this->asset = $asset;
return $this;
}
/**
* @return Asset
*/
public function getAsset()
{
return $this->asset;
}
/**
* @return Download
*/
public function setIpAddress(IpAddress $ipAddress)
{
$this->ipAddress = $ipAddress;
return $this;
}
/**
* @return IpAddress
*/
public function getIpAddress()
{
return $this->ipAddress;
}
/**
* @param int $trackingId
*
* @return Download
*/
public function setTrackingId($trackingId)
{
$this->trackingId = $trackingId;
return $this;
}
/**
* @return int
*/
public function getTrackingId()
{
return $this->trackingId;
}
/**
* @return mixed
*/
public function getLead()
{
return $this->lead;
}
/**
* @param mixed $lead
*/
public function setLead($lead): void
{
$this->lead = $lead;
}
/**
* @return mixed
*/
public function getSource()
{
return $this->source;
}
/**
* @param mixed $source
*/
public function setSource($source): void
{
$this->source = $source;
}
/**
* @return int
*/
public function getSourceId()
{
return $this->sourceId;
}
/**
* @param mixed $sourceId
*/
public function setSourceId($sourceId): void
{
$this->sourceId = (int) $sourceId;
}
/**
* @return mixed
*/
public function getEmail()
{
return $this->email;
}
/**
* @param mixed $email
*/
public function setEmail(Email $email): void
{
$this->email = $email;
}
public function getUtmCampaign(): ?string
{
return $this->utmCampaign;
}
public function setUtmCampaign(?string $utmCampaign): static
{
$this->utmCampaign = $utmCampaign;
return $this;
}
public function getUtmContent(): ?string
{
return $this->utmContent;
}
public function setUtmContent(?string $utmContent): static
{
$this->utmContent = $utmContent;
return $this;
}
public function getUtmMedium(): ?string
{
return $this->utmMedium;
}
public function setUtmMedium(?string $utmMedium): static
{
$this->utmMedium = $utmMedium;
return $this;
}
public function getUtmSource(): ?string
{
return $this->utmSource;
}
public function setUtmSource(?string $utmSource): static
{
$this->utmSource = $utmSource;
return $this;
}
public function getUtmTerm(): ?string
{
return $this->utmTerm;
}
public function setUtmTerm(?string $utmTerm): static
{
$this->utmTerm = $utmTerm;
return $this;
}
}

View File

@@ -0,0 +1,242 @@
<?php
namespace Mautic\AssetBundle\Entity;
use Doctrine\DBAL\Query\QueryBuilder;
use Mautic\CoreBundle\Entity\CommonRepository;
use Mautic\CoreBundle\Helper\Chart\PieChart;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Entity\TimelineTrait;
/**
* @extends CommonRepository<Download>
*/
class DownloadRepository extends CommonRepository
{
use TimelineTrait;
/**
* Determine if the download is a unique download.
*/
public function isUniqueDownload($assetId, $trackingId): bool
{
$q = $this->getEntityManager()->getConnection()->createQueryBuilder();
$q2 = $this->getEntityManager()->getConnection()->createQueryBuilder();
$q2->select('null')
->from(MAUTIC_TABLE_PREFIX.'asset_downloads', 'd');
$q2->where(
$q2->expr()->and(
$q2->expr()->eq('d.tracking_id', ':id'),
$q2->expr()->eq('d.asset_id', (int) $assetId)
)
);
$q->select('u.is_unique')
->from(sprintf('(SELECT (NOT EXISTS (%s)) is_unique)', $q2->getSQL()), 'u'
)
->setParameter('id', $trackingId);
return (bool) $q->executeQuery()->fetchOne();
}
/**
* Get a lead's page downloads.
*
* @param int|null $leadId
*
* @return array
*/
public function getLeadDownloads($leadId = null, array $options = [])
{
$query = $this->getEntityManager()->getConnection()->createQueryBuilder()
->select('a.id as asset_id, d.date_download as dateDownload, a.title, d.id as download_id, d.lead_id')
->from(MAUTIC_TABLE_PREFIX.'asset_downloads', 'd')
->leftJoin('d', MAUTIC_TABLE_PREFIX.'assets', 'a', 'd.asset_id = a.id');
if ($leadId) {
$query->where('d.lead_id = :leadId')
->setParameter('leadId', $leadId);
}
if (isset($options['search']) && $options['search']) {
$query->andWhere('a.title LIKE :search')
->setParameter('search', '%'.$options['search'].'%');
}
return $this->getTimelineResults($query, $options, 'a.title', 'd.date_download', [], ['date_download'], null, 'd.id');
}
/**
* Get list of assets ordered by it's download count.
*
* @param QueryBuilder $query
* @param int $limit
* @param int $offset
*
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function getMostDownloaded($query, $limit = 10, $offset = 0): array
{
$query->select('a.title, a.id, count(ad.id) as downloads')
->groupBy('a.id, a.title')
->orderBy('downloads', 'DESC')
->setMaxResults($limit)
->setFirstResult($offset);
return $query->executeQuery()->fetchAllAssociative();
}
/**
* Get list of asset referrals ordered by it's count.
*
* @param QueryBuilder $query
* @param int $limit
* @param int $offset
*
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function getTopReferrers($query, $limit = 10, $offset = 0): array
{
$query->select('ad.referer, count(ad.referer) as downloads')
->groupBy('ad.referer')
->orderBy('downloads', 'DESC')
->setMaxResults($limit)
->setFirstResult($offset);
return $query->executeQuery()->fetchAllAssociative();
}
/**
* Get pie graph data for http statuses.
*
* @param QueryBuilder $query
*
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function getHttpStatuses($query): array
{
$query->select('ad.code as status, count(ad.code) as count')
->groupBy('ad.code')
->orderBy('count', 'DESC');
$results = $query->executeQuery()->fetchAllAssociative();
$chart = new PieChart();
foreach ($results as $result) {
$chart->setDataset($result['status'], $result['count']);
}
return $chart->render();
}
/**
* @return array<mixed, array<string, mixed>>
*/
public function getDownloadCountsByPage($pageId, ?\DateTime $fromDate = null): array
{
$q = $this->_em->getConnection()->createQueryBuilder();
$q->select('count(distinct(a.tracking_id)) as count, a.source_id as id, p.title as name, p.hits as total')
->from(MAUTIC_TABLE_PREFIX.'asset_downloads', 'a')
->join('a', MAUTIC_TABLE_PREFIX.'pages', 'p', 'a.source_id = p.id');
if (is_array($pageId)) {
$q->where($q->expr()->in('p.id', $pageId))
->groupBy('p.id, a.source_id, p.title, p.hits');
} else {
$q->where($q->expr()->eq('p.id', ':page'))
->setParameter('page', (int) $pageId);
}
$q->andWhere('a.source = "page"')
->andWhere('a.code = 200');
if (null != $fromDate) {
$dh = new DateTimeHelper($fromDate);
$q->andWhere($q->expr()->gte('a.date_download', ':date'))
->setParameter('date', $dh->toUtcString());
}
$results = $q->executeQuery()->fetchAllAssociative();
$downloads = [];
foreach ($results as $r) {
$downloads[$r['id']] = $r;
}
return $downloads;
}
/**
* Get download count by email by linking emails that have been associated with a page hit that has the
* same tracking ID as an asset download tracking ID and thus assumed happened in the same session.
*
* @return array<mixed, array<string, mixed>>
*/
public function getDownloadCountsByEmail($emailId, ?\DateTime $fromDate = null): array
{
// link email to page hit tracking id to download tracking id
$q = $this->_em->getConnection()->createQueryBuilder();
$q->select('count(distinct(a.tracking_id)) as count, e.id, e.subject as name, e.variant_sent_count as total')
->from(MAUTIC_TABLE_PREFIX.'asset_downloads', 'a')
->join('a', MAUTIC_TABLE_PREFIX.'emails', 'e', 'a.email_id = e.id');
if (is_array($emailId)) {
$q->where($q->expr()->in('e.id', $emailId))
->groupBy('e.id, e.subject, e.variant_sent_count');
} else {
$q->where($q->expr()->eq('e.id', ':email'))
->setParameter('email', (int) $emailId);
}
$q->andWhere('a.code = 200');
if (null != $fromDate) {
$dh = new DateTimeHelper($fromDate);
$q->andWhere($q->expr()->gte('a.date_download', ':date'))
->setParameter('date', $dh->toUtcString());
}
$results = $q->executeQuery()->fetchAllAssociative();
$downloads = [];
foreach ($results as $r) {
$downloads[$r['id']] = $r;
}
return $downloads;
}
public function updateLeadByTrackingId($leadId, $newTrackingId, $oldTrackingId): void
{
$q = $this->_em->getConnection()->createQueryBuilder();
$q->update(MAUTIC_TABLE_PREFIX.'asset_downloads')
->set('lead_id', (int) $leadId)
->set('tracking_id', ':newTrackingId')
->where(
$q->expr()->eq('tracking_id', ':oldTrackingId')
)
->setParameters([
'newTrackingId' => $newTrackingId,
'oldTrackingId' => $oldTrackingId,
])
->executeStatement();
}
/**
* Updates lead ID (e.g. after a lead merge).
*/
public function updateLead($fromLeadId, $toLeadId): void
{
$q = $this->_em->getConnection()->createQueryBuilder();
$q->update(MAUTIC_TABLE_PREFIX.'asset_downloads')
->set('lead_id', (int) $toLeadId)
->where('lead_id = '.(int) $fromLeadId)
->executeStatement();
}
}