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,64 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Builder;
use Mautic\ReportBundle\Scheduler\Exception\InvalidSchedulerException;
use Mautic\ReportBundle\Scheduler\Exception\NotSupportedScheduleTypeException;
use Mautic\ReportBundle\Scheduler\Factory\SchedulerTemplateFactory;
use Mautic\ReportBundle\Scheduler\SchedulerInterface;
use Recurr\Exception\InvalidWeekday;
use Recurr\Rule;
use Recurr\Transformer\ArrayTransformer;
class SchedulerBuilder
{
public function __construct(
private SchedulerTemplateFactory $schedulerTemplateFactory,
) {
}
/**
* @return \Recurr\Recurrence[]|\Recurr\RecurrenceCollection
*
* @throws InvalidSchedulerException
* @throws NotSupportedScheduleTypeException
*/
public function getNextEvent(SchedulerInterface $scheduler)
{
return $this->getNextEvents($scheduler, 1);
}
/**
* @param int $count
*
* @return \Recurr\RecurrenceCollection
*
* @throws InvalidSchedulerException
* @throws NotSupportedScheduleTypeException
*/
public function getNextEvents(SchedulerInterface $scheduler, $count)
{
if (!$scheduler->isScheduled()) {
throw new InvalidSchedulerException();
}
$builder = $this->schedulerTemplateFactory->getBuilder($scheduler);
$startDate = new \DateTime();
$rule = new Rule();
if (!$scheduler->isScheduledNow()) {
$startDate->setTime(0, 0)->modify('+1 day');
}
$rule->setStartDate($startDate)->setCount($count);
try {
$finalScheduler = $builder->build($rule, $scheduler);
$transformer = new ArrayTransformer();
return $transformer->transform($finalScheduler);
} catch (InvalidWeekday) {
throw new InvalidSchedulerException();
}
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Builder;
use Mautic\ReportBundle\Scheduler\BuilderInterface;
use Mautic\ReportBundle\Scheduler\Exception\InvalidSchedulerException;
use Mautic\ReportBundle\Scheduler\SchedulerInterface;
use Recurr\Exception\InvalidArgument;
use Recurr\Rule;
class SchedulerDailyBuilder implements BuilderInterface
{
/**
* @throws InvalidSchedulerException
*/
public function build(Rule $rule, SchedulerInterface $scheduler): Rule
{
try {
$rule->setFreq('DAILY');
} catch (InvalidArgument) {
throw new InvalidSchedulerException();
}
return $rule;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Builder;
use Mautic\ReportBundle\Scheduler\BuilderInterface;
use Mautic\ReportBundle\Scheduler\Enum\SchedulerEnum;
use Mautic\ReportBundle\Scheduler\Exception\InvalidSchedulerException;
use Mautic\ReportBundle\Scheduler\SchedulerInterface;
use Recurr\Exception\InvalidArgument;
use Recurr\Exception\InvalidRRule;
use Recurr\Rule;
class SchedulerMonthBuilder implements BuilderInterface
{
/**
* @throws InvalidSchedulerException
*/
public function build(Rule $rule, SchedulerInterface $scheduler): Rule
{
try {
$frequency = $scheduler->getScheduleMonthFrequency();
$rule->setFreq('MONTHLY');
if ($scheduler->isScheduledWeekDays()) {
$days = SchedulerEnum::getWeekDays();
} else {
$days = [$scheduler->getScheduleDay()];
}
foreach ($days as $key => $day) {
$days[$key] = $frequency.$day;
}
$rule->setByDay($days);
} catch (InvalidArgument|InvalidRRule) {
throw new InvalidSchedulerException();
}
return $rule;
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Mautic\ReportBundle\Scheduler\Builder;
use Mautic\ReportBundle\Scheduler\BuilderInterface;
use Mautic\ReportBundle\Scheduler\Exception\InvalidSchedulerException;
use Mautic\ReportBundle\Scheduler\SchedulerInterface;
use Recurr\Exception\InvalidArgument;
use Recurr\Rule;
class SchedulerNowBuilder implements BuilderInterface
{
/**
* @throws InvalidSchedulerException
*/
public function build(Rule $rule, SchedulerInterface $scheduler): Rule
{
try {
$rule->setFreq('SECONDLY');
} catch (InvalidArgument) {
throw new InvalidSchedulerException();
}
return $rule;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Builder;
use Mautic\ReportBundle\Scheduler\BuilderInterface;
use Mautic\ReportBundle\Scheduler\Enum\SchedulerEnum;
use Mautic\ReportBundle\Scheduler\Exception\InvalidSchedulerException;
use Mautic\ReportBundle\Scheduler\SchedulerInterface;
use Recurr\Exception\InvalidArgument;
use Recurr\Exception\InvalidRRule;
use Recurr\Rule;
class SchedulerWeeklyBuilder implements BuilderInterface
{
/**
* @throws InvalidSchedulerException
*/
public function build(Rule $rule, SchedulerInterface $scheduler): Rule
{
try {
$rule->setFreq('WEEKLY');
if ($scheduler->isScheduledWeekDays()) {
$days = SchedulerEnum::getWeekDays();
} else {
$days = [$scheduler->getScheduleDay()];
}
$rule->setByDay($days);
} catch (InvalidArgument|InvalidRRule) {
throw new InvalidSchedulerException();
}
return $rule;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Mautic\ReportBundle\Scheduler;
use Recurr\Rule;
interface BuilderInterface
{
public function build(Rule $rule, SchedulerInterface $scheduler);
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Command;
use Mautic\ReportBundle\Exception\FileIOException;
use Mautic\ReportBundle\Model\ReportCleanup;
use Mautic\ReportBundle\Model\ReportExporter;
use Mautic\ReportBundle\Scheduler\Option\ExportOption;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
#[AsCommand(
name: 'mautic:reports:scheduler',
description: "Processes scheduler for report's export"
)]
class ExportSchedulerCommand extends Command
{
public function __construct(
private ReportExporter $reportExporter,
private ReportCleanup $reportCleanup,
private TranslatorInterface $translator,
) {
parent::__construct();
}
protected function configure()
{
$this
->addOption('--report', 'report', InputOption::VALUE_OPTIONAL, 'ID of report. Process all reports if not set.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$report = $input->getOption('report');
if (!is_null($report) && !is_numeric($report)) {
$output->writeln('<error>'.$this->translator->trans('mautic.report.schedule.command.invalid_parameter').'</error>');
return Command::INVALID;
}
try {
$exportOption = new ExportOption((int) $report);
} catch (\InvalidArgumentException $e) {
$output->writeln('<error>'.$this->translator->trans('mautic.report.schedule.command.invalid_parameter').'</error>');
return Command::SUCCESS;
}
try {
if ($exportOption->getReportId()) {
$this->reportCleanup->cleanup($exportOption->getReportId());
} else {
$this->reportCleanup->cleanupAll();
}
$this->reportExporter->processExport($exportOption);
$output->writeln('<info>'.$this->translator->trans('mautic.report.schedule.command.finished').'</info>');
} catch (FileIOException $e) {
$output->writeln('<error>'.$e->getMessage().'</error>');
}
return Command::SUCCESS;
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Date;
use Mautic\ReportBundle\Scheduler\Builder\SchedulerBuilder;
use Mautic\ReportBundle\Scheduler\Entity\SchedulerEntity;
use Mautic\ReportBundle\Scheduler\Exception\InvalidSchedulerException;
use Mautic\ReportBundle\Scheduler\Exception\NoScheduleException;
use Mautic\ReportBundle\Scheduler\Exception\NotSupportedScheduleTypeException;
use Mautic\ReportBundle\Scheduler\SchedulerInterface;
class DateBuilder
{
public function __construct(
private SchedulerBuilder $schedulerBuilder,
) {
}
/**
* @param bool $isScheduled
* @param string $scheduleUnit
* @param string $scheduleDay
* @param string $scheduleMonthFrequency
*/
public function getPreviewDays($isScheduled, $scheduleUnit, $scheduleDay, $scheduleMonthFrequency): array
{
$entity = new SchedulerEntity($isScheduled, $scheduleUnit, $scheduleDay, $scheduleMonthFrequency);
$count = $entity->isScheduledNow() ? 1 : 10;
try {
$recurrences = $this->schedulerBuilder->getNextEvents($entity, $count);
} catch (InvalidSchedulerException|NotSupportedScheduleTypeException) {
return [];
}
$dates = [];
foreach ($recurrences as $recurrence) {
$dates[] = $recurrence->getStart();
}
return $dates;
}
/**
* @return \DateTimeInterface
*
* @throws NoScheduleException
*/
public function getNextEvent(SchedulerInterface $scheduler)
{
try {
$recurrences = $this->schedulerBuilder->getNextEvent($scheduler);
} catch (InvalidSchedulerException|NotSupportedScheduleTypeException) {
throw new NoScheduleException();
}
if (empty($recurrences[0])) {
throw new NoScheduleException();
}
return $recurrences[0]->getStart();
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Entity;
use Mautic\ReportBundle\Scheduler\Enum\SchedulerEnum;
use Mautic\ReportBundle\Scheduler\SchedulerInterface;
class SchedulerEntity implements SchedulerInterface
{
/**
* @param bool $isScheduled
* @param string|null $scheduleUnit
* @param string|null $scheduleDay
* @param string|null $scheduleMonthFrequency
*/
public function __construct(
private $isScheduled,
private $scheduleUnit,
private $scheduleDay,
private $scheduleMonthFrequency,
) {
}
/**
* @return bool
*/
public function isScheduled()
{
return $this->isScheduled;
}
/**
* @return string|null
*/
public function getScheduleUnit()
{
return $this->scheduleUnit;
}
/**
* @return string|null
*/
public function getScheduleDay()
{
return $this->scheduleDay;
}
/**
* @return string|null
*/
public function getScheduleMonthFrequency()
{
return $this->scheduleMonthFrequency;
}
public function isScheduledNow(): bool
{
return SchedulerEnum::UNIT_NOW === $this->getScheduleUnit();
}
public function isScheduledDaily(): bool
{
return SchedulerEnum::UNIT_DAILY === $this->getScheduleUnit();
}
public function isScheduledWeekly(): bool
{
return SchedulerEnum::UNIT_WEEKLY === $this->getScheduleUnit();
}
public function isScheduledMonthly(): bool
{
return SchedulerEnum::UNIT_MONTHLY === $this->getScheduleUnit();
}
public function isScheduledWeekDays(): bool
{
return SchedulerEnum::DAY_WEEK_DAYS === $this->getScheduleDay();
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Enum;
class SchedulerEnum
{
public const UNIT_NOW = 'NOW';
public const UNIT_DAILY = 'DAILY';
public const UNIT_WEEKLY = 'WEEKLY'; // Defined in report.js too
public const UNIT_MONTHLY = 'MONTHLY'; // Defined in report.js too
public const DAY_MO = 'MO';
public const DAY_TU = 'TU';
public const DAY_WE = 'WE';
public const DAY_TH = 'TH';
public const DAY_FR = 'FR';
public const DAY_SA = 'SA';
public const DAY_SU = 'SU';
public const DAY_WEEK_DAYS = 'WEEK_DAYS';
public const MONTH_FREQUENCY_FIRST = '1';
public const MONTH_FREQUENCY_LAST = '-1';
public static function getUnitEnumForSelect(): array
{
return [
'mautic.report.schedule.unit.now' => self::UNIT_NOW,
'mautic.report.schedule.unit.day' => self::UNIT_DAILY,
'mautic.report.schedule.unit.week' => self::UNIT_WEEKLY,
'mautic.report.schedule.unit.month' => self::UNIT_MONTHLY,
];
}
public static function getDayEnumForSelect(): array
{
return [
'mautic.report.schedule.day.monday' => self::DAY_MO,
'mautic.report.schedule.day.tuesday' => self::DAY_TU,
'mautic.report.schedule.day.wednesday' => self::DAY_WE,
'mautic.report.schedule.day.thursday' => self::DAY_TH,
'mautic.report.schedule.day.friday' => self::DAY_FR,
'mautic.report.schedule.day.saturday' => self::DAY_SA,
'mautic.report.schedule.day.sunday' => self::DAY_SU,
'mautic.report.schedule.day.week_days' => self::DAY_WEEK_DAYS,
];
}
public static function getMonthFrequencyForSelect(): array
{
return [
'mautic.report.schedule.month_frequency.first' => self::MONTH_FREQUENCY_FIRST,
'mautic.report.schedule.month_frequency.last' => self::MONTH_FREQUENCY_LAST,
];
}
public static function getWeekDays(): array
{
return [
self::DAY_MO,
self::DAY_TU,
self::DAY_WE,
self::DAY_TH,
self::DAY_FR,
];
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Mautic\ReportBundle\Scheduler\EventListener;
use Mautic\ReportBundle\Event\ReportEvent;
use Mautic\ReportBundle\ReportEvents;
use Mautic\ReportBundle\Scheduler\Model\SchedulerPlanner;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ReportSchedulerSubscriber implements EventSubscriberInterface
{
public function __construct(
private SchedulerPlanner $schedulerPlanner,
) {
}
public static function getSubscribedEvents(): array
{
return [ReportEvents::REPORT_POST_SAVE => ['onReportSave', 0]];
}
public function onReportSave(ReportEvent $event): ReportEvent
{
$report = $event->getReport();
$this->schedulerPlanner->computeScheduler($report);
return $event;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Exception;
class InvalidSchedulerException extends \Exception
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Exception;
class NoScheduleException extends \Exception
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Exception;
class NotSupportedScheduleTypeException extends \Exception
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Exception;
class ScheduleNotValidException extends \Exception
{
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Factory;
use Mautic\ReportBundle\Scheduler\Builder\SchedulerDailyBuilder;
use Mautic\ReportBundle\Scheduler\Builder\SchedulerMonthBuilder;
use Mautic\ReportBundle\Scheduler\Builder\SchedulerNowBuilder;
use Mautic\ReportBundle\Scheduler\Builder\SchedulerWeeklyBuilder;
use Mautic\ReportBundle\Scheduler\BuilderInterface;
use Mautic\ReportBundle\Scheduler\Exception\NotSupportedScheduleTypeException;
use Mautic\ReportBundle\Scheduler\SchedulerInterface;
class SchedulerTemplateFactory
{
/**
* @throws NotSupportedScheduleTypeException
*/
public function getBuilder(SchedulerInterface $scheduler): BuilderInterface
{
if ($scheduler->isScheduledNow()) {
return new SchedulerNowBuilder();
}
if ($scheduler->isScheduledDaily()) {
return new SchedulerDailyBuilder();
}
if ($scheduler->isScheduledWeekly()) {
return new SchedulerWeeklyBuilder();
}
if ($scheduler->isScheduledMonthly()) {
return new SchedulerMonthBuilder();
}
throw new NotSupportedScheduleTypeException();
}
}

View File

@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace Mautic\ReportBundle\Scheduler\Model;
use Mautic\CoreBundle\Exception\FileInvalidException;
use Mautic\CoreBundle\Exception\FilePathException;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Helper\FilePathResolver;
use Mautic\CoreBundle\Helper\FileProperties;
use Mautic\ReportBundle\Entity\Report;
use Mautic\ReportBundle\Exception\FileTooBigException;
class FileHandler
{
private const REPORTS_DIR = 'csv_reports';
public function __construct(
private FilePathResolver $filePathResolver,
private FileProperties $fileProperties,
private CoreParametersHelper $coreParametersHelper,
) {
}
/**
* @throws FileInvalidException
* @throws FileTooBigException
*/
public function fileCanBeAttached(string $filePath): void
{
$fileSize = $this->fileProperties->getFileSize($filePath);
$maxFileSize = (int) $this->coreParametersHelper->get('report_export_max_filesize_in_bytes');
if ($fileSize > $maxFileSize) {
throw new FileTooBigException("File {$filePath} has {$fileSize} bytes which is more than the limit of {$maxFileSize} bytes.");
}
}
/**
* Zips the file and returns the path where the zip file was created.
*
* @throws FilePathException
*/
public function zipIt(string $originalFilePath): string
{
$zipFilePath = str_replace('.csv', '.zip', $originalFilePath);
$zipArchive = new \ZipArchive();
if (true === $zipArchive->open($zipFilePath, \ZipArchive::OVERWRITE | \ZipArchive::CREATE)) {
$zipArchive->addFile($originalFilePath, 'report.csv');
$zipArchive->close();
return $zipFilePath;
}
throw new FilePathException("Could not create zip archive at {$zipFilePath}. {$zipArchive->getStatusString()}");
}
public function getPathToCompressedCsvFileForReport(Report $report): string
{
return $this->getPathToCompressedCsvFileForReportId($report->getId());
}
public function getPathToCompressedCsvFileForReportId(int $reportId): string
{
return $this->getCompressedCsvFileForReportDir()."/report_{$reportId}.zip";
}
/**
* @codeCoverageIgnore as it calls PHP function only.
*/
public function compressedCsvFileForReportExists(Report $report): bool
{
$filePath = $this->getPathToCompressedCsvFileForReport($report);
return file_exists($filePath);
}
public function moveZipToPermanentLocation(Report $report, string $originalPath): void
{
$compressedCsvPath = $this->getPathToCompressedCsvFileForReport($report);
$this->filePathResolver->delete($compressedCsvPath);
$this->filePathResolver->createDirectory(dirname($compressedCsvPath));
$this->filePathResolver->move($originalPath, $compressedCsvPath);
}
public function delete(string $filePath): void
{
$this->filePathResolver->delete($filePath);
}
public function deleteCompressedCsvFileForReportId(int $reportId): void
{
$filePath = $this->getPathToCompressedCsvFileForReportId($reportId);
if (file_exists($filePath)) {
$this->delete($filePath);
}
}
public function getCompressedCsvFileForReportDir(): string
{
$reportDir = $this->coreParametersHelper->get('report_temp_dir');
return $reportDir.'/'.self::REPORTS_DIR;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Model;
use Mautic\ReportBundle\Entity\Report;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class MessageSchedule
{
public function __construct(
private TranslatorInterface $translator,
private UrlGeneratorInterface $router,
) {
}
public function getMessageForAttachedFile(Report $report): string
{
$link = $this->router->generate('mautic_report_view', ['objectId' => $report->getId()], UrlGeneratorInterface::ABSOLUTE_URL);
$date = new \DateTime();
return $this->translator->trans(
'mautic.report.schedule.email.message',
['%report_name%' => $report->getName(), '%date%' => $date->format('Y-m-d'), '%link%' => $link]
);
}
public function getMessageForLinkedFile(Report $report): string
{
$link = $this->router->generate('mautic_report_download', ['reportId' => $report->getId()], UrlGeneratorInterface::ABSOLUTE_URL);
return $this->translator->trans(
'mautic.report.schedule.email.message_file_linked',
['%report_name%' => $report->getName(), '%link%' => $link]
);
}
public function getSubject(Report $report): string
{
$date = new \DateTime();
return $this->translator->trans(
'mautic.report.schedule.email.subject',
['%report_name%' => $report->getName(), '%date%' => $date->format('Y-m-d')]
);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Model;
use Doctrine\ORM\EntityManager;
use Mautic\ReportBundle\Entity\Report;
use Mautic\ReportBundle\Entity\Scheduler;
use Mautic\ReportBundle\Entity\SchedulerRepository;
use Mautic\ReportBundle\Scheduler\Date\DateBuilder;
use Mautic\ReportBundle\Scheduler\Exception\NoScheduleException;
class SchedulerPlanner
{
/**
* @var SchedulerRepository
*/
private \Doctrine\ORM\EntityRepository $schedulerRepository;
public function __construct(
private DateBuilder $dateBuilder,
private EntityManager $entityManager,
) {
$this->schedulerRepository = $entityManager->getRepository(Scheduler::class);
}
public function computeScheduler(Report $report): void
{
$this->removeSchedulerOfReport($report);
$this->planScheduler($report);
}
private function planScheduler(Report $report): void
{
try {
$date = $this->dateBuilder->getNextEvent($report);
} catch (NoScheduleException) {
return;
}
$scheduler = new Scheduler($report, $date);
$this->entityManager->persist($scheduler);
$this->entityManager->flush();
}
private function removeSchedulerOfReport(Report $report): void
{
$scheduler = $this->schedulerRepository->getSchedulerByReport($report);
if (!$scheduler) {
return;
}
$this->entityManager->remove($scheduler);
$this->entityManager->flush();
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Model;
use Mautic\CoreBundle\Form\DataTransformer\ArrayStringTransformer;
use Mautic\EmailBundle\Helper\MailHelper;
use Mautic\ReportBundle\Entity\Scheduler;
use Mautic\ReportBundle\Event\PermanentReportFileCreatedEvent;
use Mautic\ReportBundle\Exception\FileTooBigException;
use Mautic\ReportBundle\ReportEvents;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class SendSchedule
{
private MailHelper $mailer;
public function __construct(
MailHelper $mailer,
private MessageSchedule $messageSchedule,
private FileHandler $fileHandler,
private EventDispatcherInterface $eventDispatcher,
) {
$this->mailer = $mailer->getMailer();
}
public function send(Scheduler $scheduler, $csvFilePath): void
{
$this->mailer->reset(true);
$transformer = new ArrayStringTransformer();
$report = $scheduler->getReport();
$emails = $transformer->reverseTransform($report->getToAddress());
$subject = $this->messageSchedule->getSubject($report);
$message = $this->messageSchedule->getMessageForAttachedFile($report);
try {
// Try to send the CSV file as an email attachement.
$this->fileHandler->fileCanBeAttached($csvFilePath);
$this->mailer->attachFile($csvFilePath, basename($csvFilePath), 'text/csv');
} catch (FileTooBigException) {
$zipFilePath = $this->fileHandler->zipIt($csvFilePath);
try {
// Try to send the ZIP file as an email attachement.
$this->fileHandler->fileCanBeAttached($zipFilePath);
$this->mailer->attachFile($zipFilePath, basename($zipFilePath), 'application/zip');
} catch (FileTooBigException) {
// Send the ZIP file as link in the email message.
$this->fileHandler->moveZipToPermanentLocation($report, $zipFilePath);
$message = $this->messageSchedule->getMessageForLinkedFile($report);
$event = new PermanentReportFileCreatedEvent($report);
$this->eventDispatcher->dispatch($event, ReportEvents::REPORT_PERMANENT_FILE_CREATED);
}
}
$this->mailer->setTo($emails);
$this->mailer->setSubject($subject);
$this->mailer->setBody($message);
$this->mailer->parsePlainText($message);
$this->mailer->send(true);
// Attachment file removal will be done in \Mautic\MessengerBundle\MessageHandler\RemoveReportAttachmentHandler
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Option;
class ExportOption
{
private int $reportId;
/**
* @param int|null $reportId
*/
public function __construct($reportId)
{
if (!is_null($reportId) && !is_numeric($reportId)) {
throw new \InvalidArgumentException();
}
$this->reportId = (int) $reportId;
}
public function getReportId(): int
{
return $this->reportId;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Mautic\ReportBundle\Scheduler;
interface SchedulerInterface
{
public function isScheduled();
public function isScheduledNow(): bool;
public function isScheduledDaily();
public function isScheduledWeekly();
public function isScheduledMonthly();
public function isScheduledWeekDays();
public function getScheduleDay();
public function getScheduleMonthFrequency();
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Validator;
use Symfony\Component\Validator\Constraint;
class ScheduleIsValid extends Constraint
{
public function getTargets(): string|array
{
return self::CLASS_CONSTRAINT;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Mautic\ReportBundle\Scheduler\Validator;
use Mautic\ReportBundle\Entity\Report;
use Mautic\ReportBundle\Scheduler\Builder\SchedulerBuilder;
use Mautic\ReportBundle\Scheduler\Exception\InvalidSchedulerException;
use Mautic\ReportBundle\Scheduler\Exception\NotSupportedScheduleTypeException;
use Mautic\ReportBundle\Scheduler\Exception\ScheduleNotValidException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class ScheduleIsValidValidator extends ConstraintValidator
{
public function __construct(
private SchedulerBuilder $schedulerBuilder,
) {
}
/**
* @param Report $report
*/
public function validate($report, Constraint $constraint): void
{
if (!$report->isScheduled()) {
$report->setAsNotScheduled();
return;
}
if (is_null($report->getToAddress())) {
$this->context->buildViolation('mautic.report.schedule.to_address_required')
->atPath('toAddress')
->addViolation();
}
if ($report->isScheduledDaily()) {
$report->ensureIsDailyScheduled();
$this->buildScheduler($report);
return;
}
if ($report->isScheduledWeekly()) {
try {
$report->ensureIsWeeklyScheduled();
$this->buildScheduler($report);
return;
} catch (ScheduleNotValidException) {
$this->addReportScheduleNotValidViolation();
}
}
if ($report->isScheduledMonthly()) {
try {
$report->ensureIsMonthlyScheduled();
$this->buildScheduler($report);
return;
} catch (ScheduleNotValidException) {
$this->addReportScheduleNotValidViolation();
}
}
}
private function addReportScheduleNotValidViolation(): void
{
$this->context->buildViolation('mautic.report.schedule.notValid')
->atPath('isScheduled')
->addViolation();
}
private function buildScheduler(Report $report): void
{
try {
$this->schedulerBuilder->getNextEvent($report);
return;
} catch (InvalidSchedulerException) {
$message = 'mautic.report.schedule.notValid';
} catch (NotSupportedScheduleTypeException) {
$message = 'mautic.report.schedule.notSupportedType';
}
$this->context->buildViolation($message)
->atPath('isScheduled')
->addViolation();
}
}