diff --git a/Classes/DirectMailUtility.php b/Classes/DirectMailUtility.php
index e15c90b35..41ce47d76 100644
--- a/Classes/DirectMailUtility.php
+++ b/Classes/DirectMailUtility.php
@@ -30,7 +30,10 @@
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
+use TYPO3\CMS\Core\Http\ServerRequestFactory;
+use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
/**
* Static class.
@@ -97,6 +100,22 @@ public static function getTypolinkURL(
$typolinkPageUrl = 't3://page?uid=';
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
+ // Ensure a PSR-7 request is available for ContentObjectRenderer when running in CLI/Scheduler
+ if (!empty($GLOBALS['TYPO3_REQUEST'])) {
+ $cObj->setRequest($GLOBALS['TYPO3_REQUEST']);
+ } else {
+ try {
+ $requestFactory = GeneralUtility::makeInstance(ServerRequestFactory::class);
+ $request = $requestFactory->fromGlobals();
+ // Provide applicationType so ApplicationType::fromRequest() can detect BE/FE
+ $request = $request->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE);
+ $GLOBALS['TYPO3_REQUEST'] = $request;
+ $cObj->setRequest($request);
+ } catch (\Throwable $e) {
+ // ignore: if we cannot create a request, typolink may still fail later
+ }
+ }
+
return $cObj->typolink_URL([
'parameter' => $typolinkPageUrl . $parameter,
'forceAbsoluteUrl' => $forceAbsoluteUrl,
@@ -301,7 +320,8 @@ protected static function addUserPass(string $url, array $params): string
public static function getFullUrlsForDirectMailRecord(array $row): array
{
// Finding the domain to use
- if (!$_SERVER['HTTP_HOST']) {
+ // Use empty() to avoid "Undefined array key" when running in CLI/Scheduler
+ if (empty($_SERVER['HTTP_HOST'])) {
// In CLI / Scheduler context, $_SERVER['HTTP_HOST'] can be null
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
$site = $siteFinder->getSiteByPageId((int)$row['page']);
diff --git a/Classes/Dmailer.php b/Classes/Dmailer.php
index 9c1e83be1..1b532e14a 100644
--- a/Classes/Dmailer.php
+++ b/Classes/Dmailer.php
@@ -404,7 +404,7 @@ public function sendAdvanced(array $recipientRow, string $tableNameChar): int
// Plain
$this->theParts['plain']['content'] = '';
if ($this->flagPlain) {
- $tempContentPlain = $this->getBoundaryParts($this->dmailer['boundaryParts_plain'], $recipientRow['sys_dmail_categories_list']);
+ $tempContentPlain = $this->getBoundaryParts($this->dmailer['boundaryParts_plain'], $recipientRow['sys_dmail_categories_list'] ?? '');
if ($this->mailHasContent) {
$tempContentPlain = $this->replaceMailMarkers($tempContentPlain, $recipientRow, $additionalMarkers);
if (trim($this->dmailer['sys_dmail_rec']['use_rdct']) || trim($this->dmailer['sys_dmail_rec']['long_link_mode'])) {
@@ -844,7 +844,7 @@ protected function setContent(MailMessage $mailer): void
$this->extractMediaLinks();
foreach ($this->theParts['html']['media'] as $media) {
// TODO: why are there table related tags here?
- if (in_array($media['tag'], ['img', 'table', 'tr', 'td'], true) && !$media['use_jumpurl'] && !$media['do_not_embed']) {
+ if (isset($media['tag']) && in_array($media['tag'], ['img', 'table', 'tr', 'td'], true) && !$media['use_jumpurl'] && !$media['do_not_embed']) {
if (ini_get('allow_url_fopen')) {
$context = GeneralUtility::makeInstance(FetchUtility::class)->getStreamContext();
if (($fp = fopen($media['absRef'], 'r', false, $context)) !== false) {
diff --git a/Classes/Module/MailerEngineController.php b/Classes/Module/MailerEngineController.php
index 969e8a2b2..f82a53006 100644
--- a/Classes/Module/MailerEngineController.php
+++ b/Classes/Module/MailerEngineController.php
@@ -8,6 +8,8 @@
use DirectMailTeam\DirectMail\Repository\SysDmailMaillogRepository;
use DirectMailTeam\DirectMail\Repository\SysDmailRepository;
use DirectMailTeam\DirectMail\Utility\SchedulerUtility;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException;
@@ -21,8 +23,7 @@
use TYPO3\CMS\Core\Pagination\ArrayPaginator;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+
final class MailerEngineController extends MainController
{
@@ -154,9 +155,9 @@ public function indexAction(ModuleTemplate $view): ResponseInterface
protected function getSchedulerTable(): array
{
- $schedulerTable = ['taskGroupsWithTasks' => [], 'errorClasses' => []];
+ $schedulerTable = [];
if (ExtensionManagementUtility::isLoaded('scheduler')) {
- $schedulerTable = SchedulerUtility::getDMTable();
+ $schedulerTable = GeneralUtility::makeInstance(SchedulerUtility::class)->getDMTable();
}
return $schedulerTable;
}
diff --git a/Classes/Module/StatisticsController.php b/Classes/Module/StatisticsController.php
index 75c23fc6a..85edcc5e7 100644
--- a/Classes/Module/StatisticsController.php
+++ b/Classes/Module/StatisticsController.php
@@ -1574,7 +1574,7 @@ public function getUrlStr(array $urlParts): string
$urlstr .= ($urlParts['fragment'] ?? '') ? '#' . $urlParts['fragment'] : '';
}
} else {
- $urlstr = ((isset($urlParts['host']) && $urlParts['host']) ? $urlParts['scheme'] . '://' . $urlParts['host'] : $baseUrl) . $urlParts['path'];
+ $urlstr = ((isset($urlParts['host']) && $urlParts['host']) ? $urlParts['scheme'] . '://' . $urlParts['host'] : $baseUrl) . ($urlParts['path'] ?? '');
$urlstr .= ($urlParts['query'] ?? '') ? '?' . $urlParts['query'] : '';
$urlstr .= ($urlParts['fragment'] ?? '') ? '#' . $urlParts['fragment'] : '';
}
@@ -1653,11 +1653,11 @@ public function getLinkLabel(
}
- if ($this->implodedParams['showContentTitle'] == 1) {
+ if (isset($this->implodedParams['showContentTitle']) && $this->implodedParams['showContentTitle'] == 1) {
$label = $contentTitle;
}
- if ($this->implodedParams['prependContentTitle'] == 1) {
+ if (isset($this->implodedParams['prependContentTitle']) && $this->implodedParams['prependContentTitle'] == 1) {
$label = $contentTitle . ' (' . $linkedWord . ')';
}
diff --git a/Classes/SelectCategories.php b/Classes/SelectCategories.php
index c24af3f3c..5b298bea4 100644
--- a/Classes/SelectCategories.php
+++ b/Classes/SelectCategories.php
@@ -17,6 +17,7 @@
use DirectMailTeam\DirectMail\Repository\TempRepository;
use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
@@ -39,7 +40,7 @@ public function getLocalizedCategories(array &$params): void
$lang = $this->getLang();
$site = $params['site'];
- $languages = $site->getAllLanguages();
+ $languages = ($site instanceof Site) ? $site->getAllLanguages() : [];
foreach($languages as $language) {
if($language->getLocale()->getLanguageCode() == $lang) {
$sysLanguageUid = $language->getLanguageId();
diff --git a/Classes/Utility/SchedulerUtility.php b/Classes/Utility/SchedulerUtility.php
index 222469c20..e9b83bad9 100644
--- a/Classes/Utility/SchedulerUtility.php
+++ b/Classes/Utility/SchedulerUtility.php
@@ -12,6 +12,8 @@
use DirectMailTeam\DirectMail\Repository\TempRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Scheduler\Exception\InvalidTaskException;
+use TYPO3\CMS\Scheduler\ProgressProviderInterface;
use TYPO3\CMS\Scheduler\Service\TaskService;
use TYPO3\CMS\Scheduler\Task\AbstractTask;
use TYPO3\CMS\Scheduler\Task\TaskSerializer;
@@ -19,15 +21,20 @@
class SchedulerUtility
{
- protected static function isValidTaskObject($task): bool
+ public function __construct(
+ protected readonly TaskSerializer $taskSerializer,
+ protected readonly TaskService $taskService,
+ ) {
+ }
+
+ protected function isValidTaskObject($task): bool
{
return (new TaskValidator())->isValid($task);
}
- public static function getDMTable(): array
+ public function getDMTable(): array
{
- $taskSerializer = GeneralUtility::makeInstance(TaskSerializer::class);
- $registeredClasses = GeneralUtility::makeInstance(TaskService::class)->getAvailableTaskTypes();
+ $registeredClasses = $this->taskService->getAvailableTaskTypes();
$tasks = GeneralUtility::makeInstance(TempRepository::class)->getDMTasks();
@@ -45,18 +52,18 @@ public static function getDMTable(): array
];
try {
- $taskObject = $taskSerializer->deserialize($task['serialized_task_object']);
+ $taskObject = $this->taskSerializer->deserialize($task['serialized_task_object']);
} catch (InvalidTaskException $e) {
$taskData['errorMessage'] = $e->getMessage();
- $taskData['class'] = $taskSerializer->extractClassName($task['serialized_task_object']);
+ $taskData['class'] = $this->taskSerializer->extractClassName($task['serialized_task_object']);
$errorClasses[] = $taskData;
continue;
}
- $taskClass = $taskSerializer->resolveClassName($taskObject);
+ $taskClass = $this->taskSerializer->resolveClassName($taskObject);
$taskData['class'] = $taskClass;
- if (!self::isValidTaskObject($taskObject)) {
+ if (!$this->isValidTaskObject($taskObject)) {
$taskData['errorMessage'] = 'The class ' . $taskClass . ' is not a valid task';
$errorClasses[] = $taskData;
continue;
@@ -72,15 +79,6 @@ public static function getDMTable(): array
$taskData['progress'] = round((float)$taskObject->getProgress(), 2);
}
- if (!isset($registeredClasses[$taskClass])) {
- $taskData['errorMessage'] = 'The class ' . $taskClass . ' is not a registered task';
- $errorClasses[] = $taskData;
- continue;
- }
-
- if ($taskObject instanceof ProgressProviderInterface) {
- $taskData['progress'] = round((float)$taskObject->getProgress(), 2);
- }
$taskData['classTitle'] = $registeredClasses[$taskClass]['title'];
$taskData['classExtension'] = $registeredClasses[$taskClass]['extension'];
$taskData['additionalInformation'] = $taskObject->getAdditionalInformation();
@@ -89,17 +87,21 @@ public static function getDMTable(): array
$taskData['nextExecution'] = (int)$task['nextexecution'];
$taskData['type'] = 'single';
$taskData['frequency'] = '';
+
if ($taskObject->getType() === AbstractTask::TYPE_RECURRING) {
$taskData['type'] = 'recurring';
$taskData['frequency'] = $taskObject->getExecution()->getCronCmd() ?: $taskObject->getExecution()->getInterval();
}
+
$taskData['multiple'] = (bool)$taskObject->getExecution()->getMultiple();
$taskData['lastExecutionFailure'] = false;
+
if (!empty($task['lastexecution_failure'])) {
$taskData['lastExecutionFailure'] = true;
$exceptionArray = @unserialize($task['lastexecution_failure']);
$taskData['lastExecutionFailureCode'] = '';
$taskData['lastExecutionFailureMessage'] = '';
+
if (is_array($exceptionArray)) {
$taskData['lastExecutionFailureCode'] = $exceptionArray['code'];
$taskData['lastExecutionFailureMessage'] = $exceptionArray['message'];
@@ -108,6 +110,7 @@ public static function getDMTable(): array
// If a group is deleted or no group is set it needs to go into "not assigned groups"
$groupIndex = $task['isTaskGroupDeleted'] === 1 || $task['isTaskGroupDeleted'] === null ? 0 : (int)$task['task_group'];
+
if (!isset($taskGroupsWithTasks[$groupIndex])) {
$taskGroupsWithTasks[$groupIndex] = [
'tasks' => [],
@@ -117,6 +120,7 @@ public static function getDMTable(): array
'groupHidden' => $task['isTaskGroupHidden'],
];
}
+
$taskGroupsWithTasks[$groupIndex]['tasks'][] = $taskData;
}
}
diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml
index 049bd49d4..d861aafc7 100644
--- a/Configuration/Services.yaml
+++ b/Configuration/Services.yaml
@@ -7,6 +7,9 @@ services:
DirectMailTeam\DirectMail\:
resource: '../Classes/*'
+ DirectMailTeam\DirectMail\Utility\SchedulerUtility:
+ public: true
+
DirectMailTeam\DirectMail\Module\ConfigurationController:
tags: ['backend.controller']
DirectMailTeam\DirectMail\Module\DmailController:
diff --git a/Resources/Public/Images/module-directmail.svg b/Resources/Public/Images/module-directmail.svg
old mode 100755
new mode 100644
index 0f452e7bb..841bcff5a
--- a/Resources/Public/Images/module-directmail.svg
+++ b/Resources/Public/Images/module-directmail.svg
@@ -1,10 +1 @@
-
-
-
-
+
diff --git a/composer.json b/composer.json
index 29c1757cf..1bd3de141 100644
--- a/composer.json
+++ b/composer.json
@@ -25,7 +25,7 @@
"typo3/cms-core": "^13.4",
"typo3/cms-dashboard": "^13.4",
"php": "^8.2 || ^8.3 || ^8.4",
- "friendsoftypo3/tt-address": "^9.0"
+ "friendsoftypo3/tt-address": "^9.0 || ^10.0"
},
"require-dev": {
"roave/security-advisories": "dev-master"
diff --git a/ext_emconf.php b/ext_emconf.php
index 549780f68..07c67107b 100644
--- a/ext_emconf.php
+++ b/ext_emconf.php
@@ -13,7 +13,7 @@
'depends' => [
'typo3' => '13.4.0-13.4.99',
'lowlevel' => '13.4.0-13.99.99',
- 'tt_address' => '9.0.0-9.0.99',
+ 'tt_address' => '9.0.0-10.0.99',
'php' => '8.2.0-8.4.99',
],
'conflicts' => [