Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make package ready for multiple sites and multiple Mautic instances #47

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/node_modules
.vscode
3 changes: 2 additions & 1 deletion Classes/Api/Emails.php
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ class Emails extends MauticEmails
*/
public function sendExample(int $id, array $recipients)
{
return $this->makeRequest($this->endpoint . '/' . $id . '/example', ['recipients' => $recipients], 'POST');
$uri = sprintf('%s/%s/example', $this->endpoint, $id);
return $this->makeRequest($uri, ['recipients' => $recipients], 'POST');
}
}
21 changes: 6 additions & 15 deletions Classes/Command/MauticCommandController.php
Original file line number Diff line number Diff line change
@@ -8,24 +8,15 @@
use Neos\Flow\Cli\CommandController;
use function \Neos\Flow\var_dump;

/**
*
* @Flow\Scope("singleton")
*/
// TODO Remove var_dump and replace with proper logging
#[Flow\Scope('singleton')]
class MauticCommandController extends CommandController
{
#[Flow\Inject]
protected ApiService $apiService;

/**
* @Flow\Inject
* @var ApiService
*/
protected $apiService;

/**
* @Flow\Inject
* @var MauticService
*/
protected $mauticService;
#[Flow\Inject]
protected MauticService $mauticService;

public function getCommand(string $emailIdentifier)
{
122 changes: 45 additions & 77 deletions Classes/Controller/BackendController.php
Original file line number Diff line number Diff line change
@@ -25,9 +25,7 @@
use Neos\Neos\Service\UserService;
use Neos\Neos\TypeConverter\NodeConverter;

/**
* @Flow\Scope("singleton")
*/
#[Flow\Scope('singleton')]
class BackendController extends AbstractModuleController
{
/**
@@ -40,76 +38,40 @@ class BackendController extends AbstractModuleController
*/
protected $defaultViewObjectName = FusionView::class;

/**
* @Flow\Inject
* @var Context
*/
protected $securityContext;
#[Flow\Inject]
protected Context $securityContext;

/**
* @Flow\Inject
* @var NodeService
*/
protected $nodeService;
#[Flow\Inject]
protected NodeService $nodeService;

/**
* @Flow\Inject
* @var LinkingService
*/
protected $linkingService;
#[Flow\Inject]
protected LinkingService $linkingService;

/**
* @Flow\Inject
* @var MauticService
*/
protected $mauticService;
#[Flow\Inject]
protected MauticService $mauticService;

/**
* @Flow\Inject
* @var TaskService
*/
protected $taskService;
#[Flow\Inject]
protected TaskService $taskService;

/**
* @Flow\Inject
* @var TestEmailService
*/
protected $testEmailService;
#[Flow\Inject]
protected TestEmailService $testEmailService;

/**
* @Flow\Inject
* @var ApiService
*/
protected $apiService;
#[Flow\Inject]
protected ApiService $apiService;

/**
* @Flow\Inject
* @var FlashMessageService
*/
protected $flashMessageService;
#[Flow\Inject]
protected FlashMessageService $flashMessageService;

/**
* @Flow\Inject
* @var TranslationHelper
*/
protected $translationHelper;
#[Flow\Inject]
protected TranslationHelper $translationHelper;

/**
* @Flow\Inject
* @var UserService
*/
protected $userService;
#[Flow\Inject]
protected UserService $userService;

/**
* @var array
* @Flow\InjectConfiguration(path="routeArgument", package="Garagist.Mautic")
*/
#[Flow\InjectConfiguration('routeArgument', 'Garagist.Mautic')]
protected $routeArgument;

/**
* @var string
* @Flow\InjectConfiguration(path="mail.trackingPixel", package="Garagist.Mautic")
*/
#[Flow\InjectConfiguration('mail.trackingPixel', 'Garagist.Mautic')]
protected $trackingPixel;

/**
@@ -127,25 +89,10 @@ class BackendController extends AbstractModuleController
*/
protected function initialize(): void
{
// use this constant only if available (became available with patch level releases in Neos 4.0 and up)
if (defined(NodeConverter::class . '::INVISIBLE_CONTENT_SHOWN')) {
$this->arguments->getArgument('node')->getPropertyMappingConfiguration()->setTypeConverterOption(NodeConverter::class, NodeConverter::INVISIBLE_CONTENT_SHOWN, true);
}
$this->arguments->getArgument('node')->getPropertyMappingConfiguration()->setTypeConverterOption(NodeConverter::class, NodeConverter::INVISIBLE_CONTENT_SHOWN, true);
}

protected function localSort(array $array, ?string $key = null): array
{
$collator = new Collator($this->userService->getInterfaceLanguage());

uasort($array, function ($a, $b) use ($collator, $key) {
if (isset($key)) {
return $collator->compare($a[$key], $b[$key]);
}
return $collator->compare($a, $b);
});

return $array;
}

/**
* Render the overview of emails
@@ -729,4 +676,25 @@ private function getCategories(NodeInterface $node): ?array
'nodes' => $parents,
];
}

/**
* Sort an array by the current language
*
* @param array $array
* @param string|null $key
* @return array
*/
private function localSort(array $array, ?string $key = null): array
{
$collator = new Collator($this->userService->getInterfaceLanguage());

uasort($array, function ($a, $b) use ($collator, $key) {
if (isset($key)) {
return $collator->compare($a[$key], $b[$key]);
}
return $collator->compare($a, $b);
});

return $array;
}
}
7 changes: 2 additions & 5 deletions Classes/DataSource/FormsDataSource.php
Original file line number Diff line number Diff line change
@@ -14,11 +14,8 @@ class FormsDataSource extends AbstractDataSource
*/
protected static $identifier = 'garagist-mautic-forms';

/**
* @Flow\Inject
* @var ApiService
*/
protected $apiService;
#[Flow\Inject]
protected ApiService $apiService;

/**
* Get data
5 changes: 1 addition & 4 deletions Classes/Domain/Model/MauticEmail.php
Original file line number Diff line number Diff line change
@@ -6,10 +6,7 @@
use Doctrine\ORM\Mapping as ORM;
use DateTime;

/**
*
* @Flow\Entity
*/
#[Flow\Entity]
class MauticEmail
{

4 changes: 1 addition & 3 deletions Classes/Domain/Repository/MauticEmailRepository.php
Original file line number Diff line number Diff line change
@@ -5,9 +5,7 @@
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Persistence\Doctrine\Repository;

/**
* @Flow\Scope("singleton")
*/
#[Flow\Scope('singleton')]
class MauticEmailRepository extends Repository
{
}
30 changes: 30 additions & 0 deletions Classes/EelHelper/MauticHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Garagist\Mautic\EelHelper;

use Garagist\Mautic\Service\SettingsService;
use Neos\Eel\ProtectedContextAwareInterface;
use Neos\Flow\Annotations as Flow;

class MauticHelper implements ProtectedContextAwareInterface
{

#[Flow\Inject]
protected SettingsService $settingsService;

public function settings(string $settingPath, ?string $siteName = null, $rootPackage = 'Garagist.Mautic'): string
{
return $this->settingsService->path($settingPath, $siteName, $rootPackage);
}

/**
* All methods are considered safe
*
* @param string $methodName The name of the method
* @return bool
*/
public function allowsCallOfMethod($methodName)
{
return true;
}
}
4 changes: 1 addition & 3 deletions Classes/Event/MauticEmailCreate.php
Original file line number Diff line number Diff line change
@@ -8,9 +8,7 @@
use Doctrine\ORM\Mapping as ORM;
use Neos\Flow\Annotations as Flow;

/**
* @Flow\Proxy(false)
*/
#[Flow\Proxy(false)]
class MauticEmailCreate implements DomainEventInterface
{
/**
4 changes: 1 addition & 3 deletions Classes/Event/MauticEmailDelete.php
Original file line number Diff line number Diff line change
@@ -7,9 +7,7 @@
use Neos\EventSourcing\Event\DomainEventInterface;
use Neos\Flow\Annotations as Flow;

/**
* @Flow\Proxy(false)
*/
#[Flow\Proxy(false)]
final class MauticEmailDelete implements DomainEventInterface
{
/**
4 changes: 1 addition & 3 deletions Classes/Event/MauticEmailPublish.php
Original file line number Diff line number Diff line change
@@ -7,9 +7,7 @@
use Neos\EventSourcing\Event\DomainEventInterface;
use Neos\Flow\Annotations as Flow;

/**
* @Flow\Proxy(false)
*/
#[Flow\Proxy(false)]
final class MauticEmailPublish implements DomainEventInterface
{
/**
4 changes: 1 addition & 3 deletions Classes/Event/MauticEmailSend.php
Original file line number Diff line number Diff line change
@@ -7,9 +7,7 @@
use Neos\EventSourcing\Event\DomainEventInterface;
use Neos\Flow\Annotations as Flow;

/**
* @Flow\Proxy(false)
*/
#[Flow\Proxy(false)]
final class MauticEmailSend implements DomainEventInterface
{
/**
4 changes: 1 addition & 3 deletions Classes/Event/MauticEmailSent.php
Original file line number Diff line number Diff line change
@@ -7,9 +7,7 @@
use Neos\EventSourcing\Event\DomainEventInterface;
use Neos\Flow\Annotations as Flow;

/**
* @Flow\Proxy(false)
*/
#[Flow\Proxy(false)]
final class MauticEmailSent implements DomainEventInterface
{
/**
4 changes: 1 addition & 3 deletions Classes/Event/MauticEmailSync.php
Original file line number Diff line number Diff line change
@@ -7,9 +7,7 @@
use Neos\EventSourcing\Event\DomainEventInterface;
use Neos\Flow\Annotations as Flow;

/**
* @Flow\Proxy(false)
*/
#[Flow\Proxy(false)]
final class MauticEmailSync implements DomainEventInterface
{
/**
4 changes: 1 addition & 3 deletions Classes/Event/MauticEmailTaskFinished.php
Original file line number Diff line number Diff line change
@@ -7,9 +7,7 @@
use Neos\EventSourcing\Event\DomainEventInterface;
use Neos\Flow\Annotations as Flow;

/**
* @Flow\Proxy(false)
*/
#[Flow\Proxy(false)]
final class MauticEmailTaskFinished implements DomainEventInterface
{
/**
4 changes: 1 addition & 3 deletions Classes/Event/MauticEmailUnpublish.php
Original file line number Diff line number Diff line change
@@ -7,9 +7,7 @@
use Neos\EventSourcing\Event\DomainEventInterface;
use Neos\Flow\Annotations as Flow;

/**
* @Flow\Proxy(false)
*/
#[Flow\Proxy(false)]
final class MauticEmailUnpublish implements DomainEventInterface
{
/**
4 changes: 1 addition & 3 deletions Classes/Event/MauticEmailUpdate.php
Original file line number Diff line number Diff line change
@@ -8,9 +8,7 @@
use Doctrine\ORM\Mapping as ORM;
use Neos\Flow\Annotations as Flow;

/**
* @Flow\Proxy(false)
*/
#[Flow\Proxy(false)]
class MauticEmailUpdate implements DomainEventInterface
{
/**
13 changes: 5 additions & 8 deletions Classes/FusionObjects/ApiFormImplementation.php
Original file line number Diff line number Diff line change
@@ -9,11 +9,8 @@
class ApiFormImplementation extends AbstractFusionObject
{

/**
* @Flow\Inject
* @var ApiService
*/
protected $apiService;
#[Flow\Inject]
protected ApiService $apiService;

/**
* @return string
@@ -60,12 +57,12 @@ public function evaluate()
$tagName = null;
if (in_array($type, ['email', 'password', 'text', 'file', 'date', 'datetime', 'number', 'captcha', 'url', 'tel'])) {
$tagName = 'input';
} else if (in_array($type, ['select', 'country'])) {
} elseif (in_array($type, ['select', 'country'])) {
$tagName = 'select';
} else if (in_array($type, ['radiogrp', 'checkboxgrp'])) {
} elseif (in_array($type, ['radiogrp', 'checkboxgrp'])) {
$tagName = 'inputGroup';
$value = $value ? array_map('trim', explode(',', $value)) : [];
} else if ($type == 'textarea') {
} elseif ($type == 'textarea') {
$tagName = $type;
}

8 changes: 2 additions & 6 deletions Classes/FusionObjects/PersonalizationImplementation.php
Original file line number Diff line number Diff line change
@@ -8,12 +8,8 @@

class PersonalizationImplementation extends AbstractFusionObject
{

/**
* @Flow\Inject
* @var PersonalizationService
*/
protected $personalizationService;
#[Flow\Inject]
protected PersonalizationService $personalizationService;

/**
* @return string
44 changes: 44 additions & 0 deletions Classes/FusionObjects/SettingsImplementation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Garagist\Mautic\FusionObjects;

use Neos\Flow\Annotations as Flow;
use Neos\Fusion\FusionObjects\AbstractFusionObject;
use Garagist\Mautic\Service\SettingsService;

class SettingsImplementation extends AbstractFusionObject
{
#[Flow\Inject]
protected SettingsService $settingsService;

/**
* Get setting path
*
* @return string|null
*/
protected function getPath(): ?string
{
return $this->fusionValue('path');
}

/**
* Get site name
*
* @return string|null
*/
protected function siteName(): ?string
{
return $this->fusionValue('siteName');
}

/**
* @return string
*/
public function evaluate()
{
$path = $this->getPath();
$siteName = $this->siteName();

return $this->settingsService->path($path, $siteName);
}
}
34 changes: 10 additions & 24 deletions Classes/Manager/MauticProcessManager.php
Original file line number Diff line number Diff line change
@@ -24,34 +24,20 @@

final class MauticProcessManager implements EventListenerInterface
{
/**
* @Flow\Inject
* @var MauticService
*/
protected $mauticService;
#[Flow\Inject]
protected MauticService $mauticService;

/**
* @Flow\Inject
* @var TaskService
*/
protected $taskService;
#[Flow\Inject]
protected TaskService $taskService;

/**
* @var EventStore
*/
protected $eventStore;
#[Flow\Inject]
protected EventStore $eventStore;

/**
* @Flow\Inject
* @var EventStoreFactory
*/
protected $eventStoreFactory;
#[Flow\Inject]
protected EventStoreFactory $eventStoreFactory;

/**
* @Flow\Inject(name="Garagist.Mautic:MauticLogger")
* @var LoggerInterface
*/
protected $mauticLogger;
#[Flow\Inject(name: 'Garagist.Mautic:MauticLogger')]
protected LoggerInterface $mauticLogger;

protected function initializeObject(): void
{
79 changes: 35 additions & 44 deletions Classes/Provider/DataProvider.php
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@

use Garagist\Mautic\Domain\Model\MauticEmail;
use Garagist\Mautic\Service\ApiService;
use Garagist\Mautic\Service\SettingsService;
use Garagist\Mautic\Service\MauticService;
use Garagist\Mautic\Service\PersonalizationService;
use Neos\ContentRepository\Domain\Model\NodeInterface;
@@ -16,51 +17,31 @@
use Neos\Flow\Exception;
use Psr\Log\LoggerInterface;

/**
* @Flow\Scope("singleton")
*/
#[Flow\Scope('singleton')]
class DataProvider implements DataProviderInterface
{
/**
* @var array
* @Flow\InjectConfiguration(package="Garagist.Mautic")
*/
protected $settings;
#[Flow\Inject]
protected PersonalizationService $personalizationService;

/**
* @Flow\Inject
* @var PersonalizationService
*/
protected $personalizationService;
#[Flow\Inject]
protected SettingsService $settingsService;

/**
* @Flow\Inject
* @var MauticService
*/
protected $mauticService;
#[Flow\Inject]
protected MauticService $mauticService;

/**
* @Flow\Inject(name="Garagist.Mautic:MauticLogger")
* @var LoggerInterface
*/
protected $mauticLogger;
#[Flow\Inject(name: 'Garagist.Mautic:MauticLogger')]
protected LoggerInterface $mauticLogger;

/**
* @Flow\Inject
* @var ApiService
*/
protected $apiService;
#[Flow\Inject]
protected ApiService $apiService;

/**
* @var Context
*/
protected $context;

/**
* @Flow\Inject
* @var ContextFactoryInterface
*/
protected $contextFactory;
#[Flow\Inject]
protected ContextFactoryInterface $contextFactory;

/**
* @throws Exception
@@ -139,12 +120,12 @@ public function getLanguageFromHtml(string $html): string
public function getHtml(MauticEmail $email): string
{
$content = $this->mauticService->getNewsletterTemplate($email->getProperty('htmlUrl'));
$previewText = $email->getProperty('previewText');
if ($previewText) {

if ($previewText = $email->getProperty('previewText')) {
$content = preg_replace('/<body([^>]*)>/i', '<body$1><div style="display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;"> ' . $previewText . ' </div>', $content);
}
$trackingPixel = $this->settings['mail']['trackingPixel'];
if ($trackingPixel) {

if ($trackingPixel = $this->settingsService->path('mail.trackingPixel', $email)) {
$content = str_replace('</body>', $trackingPixel . '</body>', $content);
}

@@ -192,6 +173,7 @@ public function getData(MauticEmail $email, array $segmentIds): array
{
$this->mauticLogger->debug(sprintf('Using %s DataProvider', static::class));
$node = $this->getNode($email->getNodeIdentifier());
$category = $this->settingsService->path('category.newsletter', $node);
$emailIdentifier = $email->getEmailIdentifier();

$html = $this->getHtml($email);
@@ -212,7 +194,7 @@ public function getData(MauticEmail $email, array $segmentIds): array
'title' => $title,
'name' => join('', $name),
'subject' => $subject,
'category' => (int)$this->settings['category']['newsletter'],
'category' => (int)$category,
'template' => 'blank',
'isPublished' => 0,
'customHtml' => $html,
@@ -230,7 +212,7 @@ public function getData(MauticEmail $email, array $segmentIds): array
*/
public function getPrefilledSegments(NodeInterface $node): array
{
$segmentMapping = $this->settings['segment']['mapping'];
$segmentMapping = $this->settingsService->path('segment.mapping', $node);
if (is_array($segmentMapping)) {
return $segmentMapping;
}
@@ -253,7 +235,7 @@ public function filterSegments(MauticEmail $email, array $segmentsFromMautic): a
return $choosenSegments;
}

return $this->getAllSegmentIDsFromMautic($segmentsFromMautic);
return $this->getAllSegmentIDsFromMautic($segmentsFromMautic, $email);
}

/**
@@ -283,9 +265,17 @@ protected function getNode($nodeIdentifier)
* @return array
* @throws Exception
*/
protected function getAllSegmentIDsFromMautic(array $segmentsFromMautic): array

/**
* Get all the segment IDs from Mautic
*
* @param array $segmentsFromMautic
* @param MauticEmail $email
* @return array
*/
protected function getAllSegmentIDsFromMautic(array $segmentsFromMautic, MauticEmail $email): array
{
$filteredSegments = $this->filterHiddenSegments($segmentsFromMautic);
$filteredSegments = $this->filterHiddenSegments($segmentsFromMautic, $email);
return array_map(function ($entry) {
return $entry['id'];
}, $filteredSegments);
@@ -295,11 +285,12 @@ protected function getAllSegmentIDsFromMautic(array $segmentsFromMautic): array
* Filter hidden segments
*
* @param array $segments
* @param MauticEmail $email
* @return array
*/
protected function filterHiddenSegments(array $segments): array
protected function filterHiddenSegments(array $segments, MauticEmail $email): array
{
$hiddenSegments = $this->settings['segment']['hide'];
$hiddenSegments = $this->settingsService->path('segment.hide', $email);
if (is_int($hiddenSegments)) {
$hiddenSegments = [$hiddenSegments];
}
18 changes: 5 additions & 13 deletions Classes/Service/ApiService.php
Original file line number Diff line number Diff line change
@@ -23,15 +23,10 @@
use function in_array;
use function sprintf;

/**
* @Flow\Scope("singleton")
*/
#[Flow\Scope('singleton')]
class ApiService
{
/**
* @Flow\InjectConfiguration
* @var array
*/
#[Flow\InjectConfiguration]
protected $settings = [];

/**
@@ -74,11 +69,8 @@ class ApiService
*/
protected $pageApi;

/**
* @Flow\Inject(name="Garagist.Mautic:MauticLogger")
* @var LoggerInterface
*/
protected $mauticLogger;
#[Flow\Inject(name: 'Garagist.Mautic:MauticLogger')]
protected LoggerInterface $mauticLogger;

/**
* @throws Exception
@@ -110,7 +102,7 @@ protected function initializeObject(): void
/**
* @param string $emailIdentifier
* @return void
*@throws NodeException|Exception
* @throws NodeException|Exception
*/
public function deleteEmail(string $emailIdentifier): void
{
67 changes: 19 additions & 48 deletions Classes/Service/MauticService.php
Original file line number Diff line number Diff line change
@@ -25,69 +25,40 @@
use Psr\Log\LoggerInterface;
use DateTime;

/**
* @Flow\Scope("singleton")
*/
#[Flow\Scope('singleton')]
class MauticService
{
/**
* @Flow\Inject
* @var ApiService
*/
protected $apiService;
#[Flow\Inject]
protected ApiService $apiService;

/**
* @Flow\Inject
* @var TaskService
*/
protected $taskService;
#[Flow\Inject]
protected TaskService $taskService;

/**
* @Flow\Inject
* @var MauticEmailRepository
*/
protected $mauticEmailRepository;
#[Flow\Inject]
protected MauticEmailRepository $mauticEmailRepository;

/**
* @Flow\Inject
* @var PersistenceManager
*/
protected $persistenceManager;
#[Flow\Inject]
protected PersistenceManager $persistenceManager;

/**
* @Flow\Inject
* @var EventStoreFactory
*/
protected $eventStoreFactory;
#[Flow\Inject]
protected EventStoreFactory $eventStoreFactory;

/**
* @var EventStore
*/
protected $eventStore;

/**
* @Flow\Inject(name="Garagist.Mautic:MauticLogger")
* @var LoggerInterface
*/
protected $mauticLogger;
#[Flow\Inject(name: 'Garagist.Mautic:MauticLogger')]
protected LoggerInterface $mauticLogger;

/**
* @Flow\Inject
* @var DataProviderInterface
*/
protected $dataProvider;
#[Flow\Inject]
protected DataProviderInterface $dataProvider;

/**
* @Flow\Inject
* @var TranslationHelper
*/
protected $translationHelper;
#[Flow\Inject]
protected TranslationHelper $translationHelper;

/**
* @Flow\Inject
* @var TestEmailService
*/
protected $testEmailService;
#[Flow\Inject]
protected TestEmailService $testEmailService;

protected function initializeObject(): void
{
121 changes: 102 additions & 19 deletions Classes/Service/NodeService.php
Original file line number Diff line number Diff line change
@@ -4,53 +4,137 @@

namespace Garagist\Mautic\Service;

use Neos\ContentRepository\Domain\Factory\NodeFactory;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\ContentRepository\Domain\Repository\NodeDataRepository;
use Neos\ContentRepository\Domain\Repository\WorkspaceRepository;
use Neos\ContentRepository\Domain\Service\Context;
use Neos\ContentRepository\Domain\Service\ContextFactoryInterface;
use Neos\ContentRepository\Domain\Utility\NodePaths;
use Neos\ContentRepository\Security\Authorization\Privilege\Node\NodePrivilegeSubject;
use Neos\Eel\FlowQuery\FlowQuery;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Security\Authorization\PrivilegeManagerInterface;
use Neos\Neos\Domain\Repository\DomainRepository;
use Neos\Neos\Domain\Repository\SiteRepository;
use Neos\Neos\Domain\Service\ContentDimensionPresetSourceInterface;
use Neos\Neos\Domain\Service\SiteService;
use Neos\Neos\Security\Authorization\Privilege\NodeTreePrivilege;
use Neos\ContentRepository\Domain\Projection\Content\TraversableNodeInterface;

/**
* A service for retrieving nodes from NeosCR
*
* @Flow\Scope("singleton")
* @api
*/
#[Flow\Scope('singleton')]
class NodeService
{
#[Flow\Inject]
protected ContentDimensionPresetSourceInterface $contentDimensionPresetSource;

/**
* @Flow\Inject
* @var ContentDimensionPresetSourceInterface
*/
protected $contentDimensionPresetSource;
#[Flow\Inject]
protected DomainRepository $domainRepository;

#[Flow\Inject]
protected SiteRepository $siteRepository;

#[Flow\Inject]
protected ContextFactoryInterface $contextFactory;

#[Flow\Inject]
protected PrivilegeManagerInterface $privilegeManager;

#[Flow\Inject]
protected WorkspaceRepository $workspaceRepository;

#[Flow\Inject]
protected NodeDataRepository $nodeDataRepository;

#[Flow\Inject]
protected NodeFactory $nodeFactory;

/**
* @Flow\Inject
* @var DomainRepository
* @var array
*/
protected $domainRepository;
protected array $options = [];

/**
* @Flow\Inject
* @var SiteRepository
* Get the site node from a node
*
* @param NodeInterface $node
* @return NodeInterface
*/
protected $siteRepository;
public function getSiteNodeFromNode(NodeInterface $node): NodeInterface
{
if ($siteNode = $node->getContext()->getCurrentSiteNode()) {
return $siteNode;
}

$flowQuery = new FlowQuery([$node]);
$nodes = $flowQuery->parents('[instanceof Neos.Neos:Document]')->get();

return end($nodes);
}

/**
* @Flow\Inject
* @var ContextFactoryInterface
* Get site name based on node
*
* @param NodeInterface $node
* @return string|null
*/
protected $contextFactory;
public function getSiteNameBasedOnNode(NodeInterface $node): ?string
{
if ($site = $node->getContext()->getCurrentSite()) {
return $site->getName();
}

$name = null;

try {
$nodePath = $node->findNodePath();
$nodePathSegments = $nodePath->getParts();
// Seems hacky, but we need to get the site by the node name here
// and extract the site name by the node's path
if (count($nodePathSegments) >= 3) {
$siteName = $nodePathSegments[2];
$site = $this->siteRepository->findOneByNodeName($siteName->__toString());
if ($site && $site->isOnline()) {
$name = $site->getName();
}
}
} catch (\Exception $th) {
}

return $name;
}

/**
* @var array
* Get all sites nodes
*
* @return NodeInterface[]
*/
protected array $options = [];
public function getSiteNodes() {
$siteNodes = [];
$liveWorkspace = $this->workspaceRepository->findByIdentifier('live');

foreach ($this->siteRepository->findOnline() as $site) {
$siteNodePath = NodePaths::addNodePathSegment(SiteService::SITES_ROOT_PATH, $site->getNodeName());
$siteNodesInAllDimensions = $this->nodeDataRepository->findByPathWithoutReduce($siteNodePath, $liveWorkspace);

foreach ($siteNodesInAllDimensions as $siteNodeData) {
$siteNodeContext = $this->nodeFactory->createContextMatchingNodeData($siteNodeData);
$siteNode = $this->nodeFactory->createFromNodeData($siteNodeData, $siteNodeContext);

// if the node exists, check if the user is granted access to this node
if ($this->privilegeManager->isGranted(NodeTreePrivilege::class, new NodePrivilegeSubject($siteNode))) {
$siteNodes[] = $siteNode;
break;
}
}
}

return $siteNodes;
}

/**
* Get node by id
@@ -107,7 +191,6 @@ public function getParentByType(NodeInterface $node, string $nodeTypeName): ?Nod
*/
public function getContentContextDimension(): Context
{

$dimensionName = $this->options['dimensionName'] ?? 'language';
$workspaceName = $this->options['workspaceName'] ?? 'live';

4 changes: 1 addition & 3 deletions Classes/Service/PersonalizationService.php
Original file line number Diff line number Diff line change
@@ -6,9 +6,7 @@

use Neos\Flow\Annotations as Flow;

/**
* @Flow\Scope("singleton")
*/
#[Flow\Scope('singleton')]
class PersonalizationService
{

68 changes: 68 additions & 0 deletions Classes/Service/SettingsService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace Garagist\Mautic\Service;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Configuration\ConfigurationManager;
use Garagist\Mautic\Service\NodeService;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Garagist\Mautic\Domain\Model\MauticEmail;

#[Flow\Scope('singleton')]
class SettingsService
{
#[Flow\Inject]
protected ConfigurationManager $configurationManager;

#[Flow\Inject]
protected NodeService $nodeService;

/**
* Get the path from the configuration
*
* @param string $settingPath
* @param string|NodeInterface|MauticEmail|null $siteNameOrSite
* @param string $rootPackage
* @return string
*/
public function path(string $settingPath, $sub = null, $rootPackage = 'Garagist.Mautic'): string
{
if ($sub instanceof MauticEmail) {
$sub = $this->nodeService->getNodeById($sub->getNodeIdentifier());
}
if ($sub instanceof NodeInterface) {
$sub = $this->nodeService->getSiteNameBasedOnNode($sub);
}

$siteName = is_string($sub) ? $sub : null;
$siteSettings = $this->getSetting($rootPackage, $settingPath, $siteName);

if (isset($siteSettings)) {
return $siteSettings;
}

return $this->getSetting($rootPackage, $settingPath);
}

/**
* Get the setting from the configuration
*
* @param string $settingPath
* @param string|null $siteName
* @return mixed
*/
protected function getSetting(string $rootPackage, string $settingPath, $siteName = null): mixed
{
if ($siteName && is_string($siteName)) {
$settingPath = sprintf('siteSettings.%s.%s', $siteName, $settingPath);
}
if (!str_starts_with($settingPath, $rootPackage)) {
$settingPath = sprintf('%s.%s', $rootPackage, $settingPath);
}

$setting = $this->configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_SETTINGS, $settingPath);
return $setting ?? null;
}
}
42 changes: 12 additions & 30 deletions Classes/Service/TaskService.php
Original file line number Diff line number Diff line change
@@ -27,46 +27,28 @@

class TaskService
{
/**
* @Flow\Inject(name="Garagist.Mautic:MauticLogger")
* @var LoggerInterface
*/
protected $mauticLogger;
#[Flow\Inject(name: 'Garagist.Mautic:MauticLogger')]
protected LoggerInterface $mauticLogger;

/**
* @var EventStore
*/
protected $eventStore;

/**
* @Flow\Inject
* @var MauticService
*/
protected $mauticService;
#[Flow\Inject]
protected MauticService $mauticService;

/**
* @Flow\Inject
* @var ApiService
*/
protected $apiService;
#[Flow\Inject]
protected ApiService $apiService;

/**
* @Flow\Inject
* @var MauticEmailRepository
*/
protected $mauticEmailRepository;
#[Flow\Inject]
protected MauticEmailRepository $mauticEmailRepository;

/**
* @Flow\Inject
* @var PersistenceManager
*/
protected $persistenceManager;
#[Flow\Inject]
protected PersistenceManager $persistenceManager;

/**
* @Flow\Inject
* @var EventStoreFactory
*/
protected $eventStoreFactory;
#[Flow\Inject]
protected EventStoreFactory $eventStoreFactory;

/**
* @return void
13 changes: 4 additions & 9 deletions Classes/Service/TestEmailService.php
Original file line number Diff line number Diff line change
@@ -7,22 +7,17 @@
use Neos\Flow\Annotations as Flow;
use Neos\Neos\Domain\Service\UserService;

/**
* @Flow\Scope("singleton")
*/
#[Flow\Scope('singleton')]
class TestEmailService
{
/**
* @var array|string
* @Flow\InjectConfiguration(path="testMail.recipients")
*/
#[Flow\InjectConfiguration('testMail.recipients')]
protected $recipientsFromSettings;

/**
* @Flow\Inject
* @var UserService
*/
protected $userService;
#[Flow\Inject]
protected UserService $userService;

public function getTestEmailRecipients(): array
{
37 changes: 35 additions & 2 deletions Configuration/Policy.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,43 @@
privilegeTargets:
'Neos\Flow\Security\Authorization\Privilege\Method\MethodPrivilege':
'Garagist.Mautic:BackendModule':
'Garagist.Mautic:Backend.Methods':
matcher: 'method(public Garagist\Mautic\Controller\BackendController->(.*)Action())'
'Neos\Neos\Security\Authorization\Privilege\ModulePrivilege':
'Garagist.Mautic:Backend.Module':
matcher: 'management/mautic'
'Neos\ContentRepository\Security\Authorization\Privilege\Node\EditNodePropertyPrivilege':
'Garagist.Mautic:Backend.Properties':
matcher: 'nodePropertyIsIn(["mauticPreviewText"])'
'Garagist.Mautic:Backend.Forms':
matcher: 'nodePropertyIsIn(["mauticFormId"])'
'Garagist.Mautic:Backend.Tracking':
matcher: 'nodePropertyIsIn(["mauticDoNotTrack"])'

roles:
'Neos.Neos:AbstractEditor':
privileges:
- privilegeTarget: 'Garagist.Mautic:BackendModule'
- privilegeTarget: 'Garagist.Mautic:Backend.Methods'
permission: GRANT

'Garagist.Mautic:Forms':
label: 'Mautic: Create Forms'
description: Grants access to create forms with Mautic
privileges:
- privilegeTarget: 'Garagist.Mautic:Backend.Forms'
permission: GRANT

'Garagist.Mautic:Tracking':
label: 'Mautic: Setting Tracking properties'
description: Grants access to a property to disable Tracking for Mautic
privileges:
- privilegeTarget: 'Garagist.Mautic:Backend.Tracking'
permission: GRANT

'Garagist.Mautic:Mails':
label: 'Mautic: Create emails'
description: Grants access to the backend module to create emails with Mautic
privileges:
- privilegeTarget: 'Garagist.Mautic:Backend.Module'
permission: GRANT
- privilegeTarget: 'Garagist.Mautic:Backend.Properties'
permission: GRANT
7 changes: 7 additions & 0 deletions Configuration/Settings.Garagist.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
Garagist:
Mautic:
# Override any of the settings below by add the site name
# Example:
# siteSettings:
# rootNodeName:
# enableTracking: true
siteSettings: null

routeArgument:
htmlTemplate: email
plaintextTemplate: plaintext
3 changes: 3 additions & 0 deletions Configuration/Settings.Neos.yaml
Original file line number Diff line number Diff line change
@@ -19,6 +19,9 @@ Neos:
logFileURL: '%FLOW_PATH_DATA%Logs/Mautic.log'
createParentDirectories: true
severityThreshold: '%LOG_DEBUG%'
Fusion:
defaultContext:
Mautic: 'Garagist\Mautic\EelHelper\MauticHelper'
Neos:
userInterface:
translation:
56 changes: 27 additions & 29 deletions Resources/Private/Fusion/Component/Countries.fusion
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
prototype(Garagist.Mautic:Countries) < prototype(Neos.Fusion:Component) {
value = null

countries = Neos.Fusion:DataStructure {
europe {
label = 'Europe'
@@ -263,36 +265,32 @@ prototype(Garagist.Mautic:Countries) < prototype(Neos.Fusion:Component) {
}
}

value = null

renderer = Neos.Fusion:Component {
_translatedCountries = Neos.Fusion:Map {
@process.filter = ${Array.filter(value)}
items = ${props.countries}
itemRenderer = Neos.Fusion:DataStructure {
@if.hasCountries = ${Carbon.Array.check(item.items)}
label = ${Translation.translate(itemKey, item.label, [], 'Countries', 'Garagist.Mautic')}
items = Neos.Fusion:Map {
items = ${item.items}
keyRenderer = ${itemKey}
itemRenderer = ${Translation.translate(itemKey, item, [], 'Countries', 'Garagist.Mautic')}
}
@private.translatedCountries = Neos.Fusion:Map {
@process.filter = ${Array.filter(value)}
items = ${props.countries}
itemRenderer = Neos.Fusion:DataStructure {
@if.hasCountries = ${Carbon.Array.check(item.items)}
label = ${Translation.translate(itemKey, item.label, [], 'Countries', 'Garagist.Mautic')}
items = Neos.Fusion:Map {
items = ${item.items}
keyRenderer = ${itemKey}
itemRenderer = ${Translation.translate(itemKey, item, [], 'Countries', 'Garagist.Mautic')}
}
}

renderer = afx`
<Neos.Fusion:Loop items={props._translatedCountries}>
<optgroup label={item.label}>
<Neos.Fusion:Loop items={Array.sort(item.items)}>
<option
value={itemKey}
selected={itemKey == props.value}
>
{item}
</option>
</Neos.Fusion:Loop>
</optgroup>
</Neos.Fusion:Loop>
`
}

renderer = afx`
<Neos.Fusion:Loop items={private.translatedCountries}>
<optgroup label={item.label}>
<Neos.Fusion:Loop items={Array.sort(item.items)}>
<option
value={itemKey}
selected={itemKey == props.value}
>
{item}
</option>
</Neos.Fusion:Loop>
</optgroup>
</Neos.Fusion:Loop>
`
}
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ prototype(Garagist.Mautic:Component.Form.Fragment.API) < prototype(Neos.Fusion:C
class = null
renderPrototype = ${Configuration.Setting('Garagist.Mautic.apiRenderer')}

@if.hasUrl_Id = ${this.url && this.id && this.renderPrototype}
@if.hasUrlId = ${this.url && this.id && this.renderPrototype}

renderer = Neos.Fusion:Renderer {
type = ${props.renderPrototype}
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ prototype(Garagist.Mautic:Component.Form.Fragment.Iframe) < prototype(Neos.Fusio
url = null
class = ${this.iframeClass}

@if.hasUrl_Id = ${this.url && this.id}
@if.hasUrlId = ${this.url && this.id}

renderer = afx`
<iframe src={props.url + "/form/" + props.id} class={props.class}></iframe>
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ prototype(Garagist.Mautic:Component.Form.Fragment.Javascript) < prototype(Neos.F
id = null
url = null

@if.hasUrl_Id = ${this.url && this.id}
@if.hasUrlId = ${this.url && this.id}

renderer = afx`
<script type="text/javascript" src={props.url + "/form/generate.js?id=" + props.id} defer></script>
26 changes: 14 additions & 12 deletions Resources/Private/Fusion/Component/Form/Fragment/Plain.fusion
Original file line number Diff line number Diff line change
@@ -13,27 +13,29 @@ prototype(Garagist.Mautic:Component.Form.Fragment.Plain) < prototype(Neos.Fusion
apiUrl = null
removeStyles = true

@if.hasUrl_Id_ApiUrl = ${this.url && this.id && this.apiUrl}
@if.hasUrlIdApiUrl = ${this.url && this.id && this.apiUrl}

_javascript = ${this.url + '/media/js/mautic-form.js'}
_embededFile = ${File.readFile(this.apiUrl + "/form/embed/" + this.id)}
_embededFile.@process.removeStyles = ${this.removeStyles && value ? String.pregReplace(value, '~<style([.\s\S]*?)</style>~', ''): value}
_hasReplacements = ${Carbon.Array.check(this.replacements)}
_globalVariables = ${'window.MauticDomain="' + this.url + '";window.MauticLang={submittingMessage:"' + this.waitMessage + '"}'}
@private {
javascript = ${props.url + '/media/js/mautic-form.js'}
embededFile = ${File.readFile(props.apiUrl + "/form/embed/" + props.id)}
embededFile.@process.removeStyles = ${props.removeStyles && value ? String.pregReplace(value, '~<style([.\s\S]*?)</style>~', ''): value}
hasReplacements = ${Carbon.Array.check(props.replacements)}
globalVariables = ${'window.MauticDomain="' + props.url + '";window.MauticLang={submittingMessage:"' + props.waitMessage + '"}'}
}

renderer = afx`
<script data-slipstream>{props._globalVariables}</script>
<script src={props._javascript} data-slipstream defer onload="MauticSDK.onLoad()"></script>
{props._hasReplacements ? '' : props._embededFile}
<script data-slipstream>{private.globalVariables}</script>
<script src={private.javascript} data-slipstream defer onload="MauticSDK.onLoad()"></script>
{private.hasReplacements ? '' : private.embededFile}
<Neos.Fusion:Reduce
@if={props._hasReplacements}
@if={private.hasReplacements}
items={props.replacements}
initialValue={props._embededFile}
initialValue={private.embededFile}
itemReducer={String.replace(carry, item.search, item.replace)}
/>
`

@context.cacheEntryIdentifier = ${'MauticFormPlain'+ this.url + this.id + this.apiUrl}
@context.cacheEntryIdentifier = ${'MauticFormPlain' + this.url + this.id + this.apiUrl}

@cache {
mode = 'cached'
6 changes: 6 additions & 0 deletions Resources/Private/Fusion/Helper/Settings.fusion
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
prototype(Garagist.Mautic:Helper.Settings) {
@class = 'Garagist\\Mautic\\FusionObjects\\SettingsImplementation'

siteName = ${site.name}
path = null
}
95 changes: 0 additions & 95 deletions Resources/Private/Modules/Component/ActionButton.fusion

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
prototype(Garagist.Mautic:Component.Checkbox) < prototype(Neos.Fusion:Component) {
prototype(Garagist.Mautic:Backend.Atom.Checkbox) < prototype(Neos.Fusion:Component) {
xModel = null
value = null
label = null
@@ -9,7 +9,7 @@ prototype(Garagist.Mautic:Component.Checkbox) < prototype(Neos.Fusion:Component)
xDisabled = null

renderer = afx`
<label class={Carbon.String.merge(props.class, "!p-0 !flex items-center justify-start mt-2 cursor-pointer group has-[:disabled]:cursor-not-allowed")}>
<label class={Tailwind.merge(props.class, "!p-0 !flex items-center justify-start mt-2 cursor-pointer group has-[:disabled]:cursor-not-allowed")}>
<input
x-model={!props.number && !props.boolean ? props.xModel : null}
"x-model.boolean"={!props.number && props.boolean ? props.xModel : null}
34 changes: 34 additions & 0 deletions Resources/Private/Modules/Component/Atom/Logo.fusion
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
prototype(Garagist.Mautic:Backend.Atom.Logo) < prototype(Neos.Fusion:Component) {
url = null
active = null

@private {
label = ${props.active == null ? null : Translation.translate(props.active ? 'status.active' : 'status.inactive', '', [], 'Module', 'Garagist.Mautic')}
logo = ${props.active == null || props.active ? 'Logo.svg' : 'LogoInactive.svg'}
}

renderer = afx`
<h1>
<Carbon.Eel:Tag
tagName={props.url ? 'a' : null}
attributes.href={props.url}
attributes.target="_blank"
attributes.class="inline-block"
>
<span class="sr-only">Mautic</span>
<span
class="inline-block"
aria-label={private.label}
x-tooltip={!!private.label}
>
<img
width="152"
alt="Mautic Logo"
aria-hidden="true"
src={StaticResource.uri("Garagist.Mautic", "Public/Assets/" + private.logo)}
/>
</span>
</Carbon.Eel:Tag>
</h1>
`
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
prototype(Garagist.Mautic:Component.PreviewText) < prototype(Neos.Fusion:Component) {
prototype(Garagist.Mautic:Backend.Atom.PreviewText) < prototype(Neos.Fusion:Component) {
node = ${node}
mauticPreviewText = ${q(this.node).property('mauticPreviewText')}
metaDescription = ${q(this.node).property('metaDescription')}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
prototype(Garagist.Mautic:Component.ToggleSwitch) < prototype(Neos.Fusion:Component) {
prototype(Garagist.Mautic:Backend.Atom.ToggleSwitch) < prototype(Neos.Fusion:Component) {
label = null
toggle = null
showLabel = true
88 changes: 88 additions & 0 deletions Resources/Private/Modules/Component/Block/ActionButton.fusion
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
prototype(Garagist.Mautic:Backend.Block.ActionButton) < prototype(Neos.Fusion:Component) {
detailView = false
action = null
actionArguments = null

/// true|string neos button modifier (e.g. 'primary')
footer = false

confirm = false
initalFocusOnConfirm = ${this.confirm}

/// 'success'|'warning'|'danger'
type = null

showText = false
labelKey = null

icon = null

content = null

@private {
label = ${Translation.translate(props.labelKey, null, [], 'Module', 'Garagist.Mautic')}
href = Neos.Fusion:ActionUri {
@if.hasAction = ${props.action}
action = ${props.action}
arguments = ${props.actionArguments}
}
class = Neos.Fusion:Match {
@subject = ${props.showText ? 'text' : props.type}
@default = 'neos-button hover:!bg-neos-blue focus:!bg-neos-blue'
danger = 'neos-button hover:!bg-neos-red focus:!bg-neos-red'
warning = 'neos-button hover:!bg-neos-orange focus:!bg-neos-orange'
success = 'neos-button hover:!bg-neos-green focus:!bg-neos-green'
text = 'group flex items-center justify-start'
}
iconClass = Neos.Fusion:Match {
@process.addBase = ${'flex items-center justify-center size-10 ' + value}
@subject = ${props.type}
@default = 'group-hover:bg-neos-blue group-focus:bg-neos-blue'
danger = 'group-hover:bg-neos-red group-focus:bg-neos-red'
warning = 'group-hover:bg-neos-orange group-focus:bg-neos-orange'
success = 'group-hover:bg-neos-green group-focus:bg-neos-green'
}
footerClass = ${'neos-button neos-button-' + (Type.isString(props.footer) ? props.footer : 'secondary')}
confirmAttributes = Neos.Fusion:DataStructure
}

renderer = afx`
<a
@if={!props.content && !props.confirm}
href={private.href}
class={props.footer ? private.footerClass : private.class}
aria-label={props.footer || props.showText ? null : private.label}
>
<i @if={!props.footer} class={["fas", props.icon, props.showText ? private.iconClass : null]}></i>
<span @if={props.showText && !props.footer} class="block pl-2">
{private.label}
</span>
{props.footer ? private.label : ''}
</a>
<button
@if={props.content || props.confirm}
x-data="{open:false}"
x-on:click="open=true"
class={props.footer ? private.footerClass : private.class}
aria-label={props.footer || props.showText ? null : private.label}
>
<i @if={!props.footer} class={["fas", props.icon, props.showText ? private.iconClass : null]}></i>
<span @if={props.showText && !props.footer} class="block pl-2">
{private.label}
</span>
{props.footer ? private.label : ''}
<Garagist.Mautic:Backend.Block.Modal
title={props.dialogTitle || Translation.translate(props.labelKey + '.headline', private.label, [], 'Module', 'Garagist.Mautic')}
description={props.confirm ? Translation.translate(props.labelKey + '.text', private.label, [], 'Module', 'Garagist.Mautic') : null}
confirmType={props.type}
initalFocusOnConfirm={props.initalFocusOnConfirm}
maxWidth={props.dialogMaxWidth}
xData={props.dialogXData}
xEffect={props.dialogxEffect}
content={props.content}
confirmLabel={private.label}
confirmAttributes={props.action ? {href: private.href} : private.confirmAttributes}
/>
</button>
`
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
prototype(Garagist.Mautic:Component.FlashMessages) < prototype(Neos.Fusion:Tag) {
prototype(Garagist.Mautic:Backend.Block.FlashMessages) < prototype(Neos.Fusion:Tag) {
@if.hasMessages = ${Carbon.Array.check(flashMessages)}

attributes {
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
prototype(Garagist.Mautic:Component.IndexLink) < prototype(Neos.Fusion:Component) {
prototype(Garagist.Mautic:Backend.Block.IndexLink) < prototype(Neos.Fusion:Component) {
count = null
title = null
class = 'text-sm'
@@ -9,9 +9,9 @@ prototype(Garagist.Mautic:Component.IndexLink) < prototype(Neos.Fusion:Component
x-show={props.show}
x-transition={!!props.show}
style={props.show ? "display:none" : null}
class={[props.class, "relative rounded bg-neos-gray-medium shadow-sm flex items-center hover:bg-neos-gray-darker focus-within:bg-neos-gray-darker"]}
class={Tailwind.merge(props.class, "relative rounded bg-neos-gray-medium shadow-sm flex items-center hover:bg-neos-gray-darker focus-within:bg-neos-gray-darker")}
>
<div class={[props.count ? "bg-green-800" : "bg-red-800" ,"leading-none flex-shrink-0 h-16 w-16 rounded-l flex flex-col items-center justify-center"]}>
<div class={Tailwind.merge(props.count ? "bg-green-800" : "bg-red-800", "leading-none flex-shrink-0 h-16 w-16 rounded-l flex flex-col items-center justify-center")}>
<span class="text-lg">{props.count}</span>
<span class="text-xs">
{Translation.translate('emails.count', null, [], 'Module', 'Garagist.Mautic', props.count)}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
prototype(Garagist.Mautic:Component.Modal) < prototype(Neos.Fusion:Component) {
prototype(Garagist.Mautic:Backend.Block.Modal) < prototype(Neos.Fusion:Component) {
title = null
description = null
content = null

confirmAttributes = Neos.Fusion:DataStructure {

}
confirmAttributes = Neos.Fusion:DataStructure
initalFocusOnConfirm = false
confirmLabel = 'OK'
confirmType = null
@@ -32,7 +30,7 @@ prototype(Garagist.Mautic:Component.Modal) < prototype(Neos.Fusion:Component) {
x-effect={props.xEffect}
x-dialog:panel
x-transition
style={props.maxWidth ? 'max-width: ' + props.maxWidth : null}
style={props.maxWidth ? 'max-width:' + props.maxWidth : null}
class="relative max-w-2xl w-full bg-neos-gray-dark border border-neos-gray-light shadow-lg overflow-y-auto p-4 space-y-4"
>
<!-- Header -->
162 changes: 0 additions & 162 deletions Resources/Private/Modules/Component/MailProperties.fusion

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
prototype(Garagist.Mautic:Component.EmailActionButtons) < prototype(Neos.Fusion:Component) {
prototype(Garagist.Mautic:Backend.Module.EmailActionButtons) < prototype(Neos.Fusion:Component) {
action = ${Configuration.Setting('Garagist.Mautic.action')}
detailView = false
ping = ${ping}

_buttonClass = 'whitespace-nowrap neos-button !bg-neos-gray-medium '
_default = 'hover:!bg-neos-blue focus:!bg-neos-blue'
_canResend = ${this.action.resend && this.canResend}
@if.hasPing = ${this.ping}

@if.hasPing = ${ping}

renderer = Neos.Fusion:Component {
@apply.props = ${props}
_actions = Neos.Fusion:DataStructure {
@if.ping = ${ping}
@private {
buttonClass = 'whitespace-nowrap neos-button !bg-neos-gray-medium '
default = 'hover:!bg-neos-blue focus:!bg-neos-blue'
canResend = ${props.action.resend && private.canResend}
actions = Neos.Fusion:DataStructure {
@if.ping = ${props.ping}
@process.filter = ${Array.filter(value)}
mailProperties = afx`
<Garagist.Mautic:Component.MailProperties
@if={props.action.edit && props.canEdit}
subject={props.subject}
previewText={props.previewText}
segments={props.segments}
class={props._buttonClass + props._default}
class={private.buttonClass + private.default}
detailView={props.detailView}
{...props.hrefArguments}
/>
`
sendTestMail = afx`
<Garagist.Mautic:Component.SendTestMail
<Garagist.Mautic:Backend.Module.SendTestMail
@if={props.action.test}
@if={props.canTest || props._canResend}
@if={props.canTest || private.canResend}
detailView={props.detailView}
{...props.hrefArguments}
/>
@@ -46,8 +45,8 @@ prototype(Garagist.Mautic:Component.EmailActionButtons) < prototype(Neos.Fusion:
icon = 'fa-upload'
}
send = afx`
<Garagist.Mautic:Component.SendMail
@if={(props.action.send && props.canSend) || props._canResend}
<Garagist.Mautic:Backend.Module.SendMail
@if={(props.action.send && props.canSend) || private.canResend}
action='send'
resend={props.canResend}
detailView={props.detailView}
@@ -62,7 +61,7 @@ prototype(Garagist.Mautic:Component.EmailActionButtons) < prototype(Neos.Fusion:
icon = 'fa-download'
}
publishAndSend = afx`
<Garagist.Mautic:Component.SendMail
<Garagist.Mautic:Backend.Module.SendMail
@if={props.action.publishAndSend && props.canPublishAndSend}
action='publishAndSend'
detailView={props.detailView}
@@ -92,17 +91,17 @@ prototype(Garagist.Mautic:Component.EmailActionButtons) < prototype(Neos.Fusion:
confirm = true
}
}

renderer = afx`
<Neos.Fusion:Loop items={props._actions}>
{Type.isString(item) ? item : ''}
<Garagist.Mautic:Component.ActionButton
@if={!Type.isString(item)}
showText={props.detailView}
actionArguments={props.hrefArguments}
{...item}
/>
</Neos.Fusion:Loop>
`
}

renderer = afx`
<Neos.Fusion:Loop items={private.actions}>
{Type.isString(item) ? item : ''}
<Garagist.Mautic:Backend.Block.ActionButton
@if={!Type.isString(item)}
showText={props.detailView}
actionArguments={props.hrefArguments}
{...item}
/>
</Neos.Fusion:Loop>
`
}
148 changes: 148 additions & 0 deletions Resources/Private/Modules/Component/Module/MailProperties.fusion
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
prototype(Garagist.Mautic:Component.MailProperties) < prototype(Neos.Fusion:Component) {
// If email is set the action will be edit, otherwise it will be create
email = null

// Redirect after the action is done
redirect = null

subject = null
previewText = null
previewTextPlaceholder = Garagist.Mautic:Backend.Atom.PreviewText
segments = null

chooseSegment = ${Configuration.Setting('Garagist.Mautic.segment.choose')}
chooseSegment.@process.convert = ${Type.isArray(value) ? value : Type.isInteger(value) ? [value] : null}
allSegments = ${allSegments}
prefilledSegments = ${prefilledSegments}
lockPrefilledSegments = ${Configuration.Setting('Garagist.Mautic.segment.lockPrefilled')}

node = ${node}
ping = ${ping}

class = 'neos-button neos-button-primary'

@if.hasNodeAndPing = ${this.node && this.ping}

@private {
isEdit = ${!!props.email}
type = ${private.isEdit ? 'edit' : 'create'}
showSegments = ${Carbon.Array.check(props.chooseSegment) && Carbon.Array.check(props.allSegments)}
placeholder = ${q(props.node).property('titleOverride') || q(props.node).property('title')}
i18n = ${Translation.id('').package('Garagist.Mautic').source('Module')}
}

renderer = Garagist.Mautic:Backend.Block.ActionButton {
label = ${private.i18n.id('email.' + private.type + (private.isEdit && private.showSegments ? '.withSegments' : '')).translate()}
icon = 'fa-pencil-alt'
showText = ${props.detailView}
footer = ${private.isEdit ? false : 'primary'}
dialogTitle = ${this.label}
dialogXData = Neos.Fusion:DataStructure {
@process.alpine = ${AlpineJS.object(value)}
action = Neos.Fusion:ActionUri {
action = ${private.type}
arguments = Neos.Fusion:DataStructure {
node = ${props.node}
email = ${props.email}
redirect = ${props.redirect}
}
}
subject = ${props.subject == private.placeholder ? '' : props.subject}
placeholder = ${private.placeholder}
previewText = ${props.previewText == props.previewTextPlaceholder ? '' : props.previewText}
previewTextPlaceholder = ${props.previewTextPlaceholder || ''}
segments = ${props.segments || props.prefilledSegments}
segments.@if.show = ${private.showSegments}
}
confirmLabel = ${this.label}
initalFocusOnConfirm = ${!private.isEdit}
confirmAttributes = Neos.Fusion:DataStructure {
:href = ${"action + '&moduleArguments[subject]='+(subject||placeholder)+'&moduleArguments[previewText]='+(previewText||previewTextPlaceholder)" + (private.showSegments ? "+'&moduleArguments[segments]='+segments" : '')}
:class = "segments&&!segments.length&&'pointer-events-none opacity-60'"
}
content = afx`
<fieldset class="!p-0">
<label for="subject">{private.i18n.id('subject').translate()}</label>
<input
type="text"
x-dialog:focus={private.isEdit}
x-model="subject"
id="subject"
:placeholder="placeholder"
class="block w-full"
/>
</fieldset>
<fieldset class="!p-0">
<label for="preview">
{Translation.translate('properties.mauticPreviewText', null, [], 'NodeTypes/Mixin/Email', 'Garagist.Mautic')}
</label>
<div class="auto-grow-textarea">
<textarea x-model="previewText" :placeholder="previewTextPlaceholder"></textarea>
<span>
<span x-text="previewText || previewTextPlaceholder"></span>
<br />
</span>
</div>
<div class="block my-2 opacity-60">{Translation.translate('properties.mauticPreviewText.ui.help.message', null, [], 'NodeTypes/Mixin/Email', 'Garagist.Mautic')}</div>
<div @if={private.isEdit} class="block my-2 opacity-60">{Translation.translate('email.need.refresh', null, [], 'Module', 'Garagist.Mautic')}</div>
</fieldset>
<fieldset @if={private.showSegments} class="!p-0 !mt-8 flex flex-col items-start">
<legend class="!p-0 !leading-5 !text-sm">
{private.i18n.id('recipients').translate()}
</legend>
<Neos.Fusion:Loop items={props.chooseSegment} itemName="segment">
<!-- We have a group -->
<div
@if={Carbon.Array.check(segment)}
class="bg-neos-gray-darker -mx-4 p-4 my-2 self-stretch flex flex-col items-start"
>
<!-- Prefilled group -->
<Neos.Fusion:Loop
@if={Carbon.Array.check(Carbon.Array.intersect(props.prefilledSegments, segment))}
items={segment}
itemName="subsegment"
>
<Garagist.Mautic:Backend.Atom.Checkbox
xModel="segments"
number={true}
value={subsegment}
label={props.allSegments[subsegment].name}
xDisabled={"const intersect=[" + Array.join(segment, ",") + "].filter(item=>segments.includes(item));return intersect.length<=1 && intersect[0]==" + subsegment}
/>
</Neos.Fusion:Loop>
<p @if={Carbon.Array.check(Carbon.Array.intersect(props.prefilledSegments, segment))} class="block mt-2 -mb-3 opacity-60 text-xs">
{private.i18n.id('email.oneItemNeeded').translate()}
</p>

<!-- No Prefilled group -->
<Neos.Fusion:Loop
@if={!Carbon.Array.check(Carbon.Array.intersect(props.prefilledSegments, segment))}
items={segment}
itemName="subsegment"
>
<Garagist.Mautic:Backend.Atom.Checkbox
xModel="segments"
number={true}
value={subsegment}
label={props.allSegments[subsegment].name}
/>
</Neos.Fusion:Loop>
</div>

<!-- No Group -->
<Garagist.Mautic:Backend.Atom.Checkbox
@if={!Type.isArray(segment)}
xModel="segments"
disabled={props.lockPrefilledSegments && Array.indexOf(props.prefilledSegments, segment) != -1}
number={true}
value={segment}
label={props.allSegments[segment].name}
/>
</Neos.Fusion:Loop>
<div x-show="!segments.length" x-transition class="bg-neos-red text-white p-4 mt-4 mb-2 w-full">
{private.i18n.id('pleaseChooseMinimalOneItem').translate()}
</div>
</fieldset>
`
}
}
98 changes: 98 additions & 0 deletions Resources/Private/Modules/Component/Module/SendMail.fusion
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
prototype(Garagist.Mautic:Backend.Module.SendMail) < prototype(Neos.Fusion:Component) {
email = null

// Redirect after the action is done
redirect = null
action = 'send'

class = 'neos-button neos-button-primary'
footer = false
allSegments = ${allSegments}
canSendLater = ${Configuration.Setting('Garagist.Mautic.action.sendLater')}
node = ${node}

@if.hasEmail = ${this.email}

@private {
i18n = ${Translation.id('').package('Garagist.Mautic').source('Module')}
sendDateLabel = ${private.i18n.id('email.send.date').translate()}
hasSegments = ${Carbon.Array.check(props.segments)}
}

renderer = Garagist.Mautic:Backend.Block.ActionButton {
// class in xlf file: 'block' Don't remove this as it is needed for tailwindcss to get the needed class name
label = ${private.i18n.id(props.resend ? 'email.resend' : 'email.send').translate()}
icon = 'fa-paper-plane'
showText = ${props.detailView}
dialogTitle = ${this.label}
dialogXData = Neos.Fusion:DataStructure {
@process.alpine = ${AlpineJS.object(value)}
action = Neos.Fusion:ActionUri {
action = ${props.action}
arguments = Neos.Fusion:DataStructure {
node = ${props.node}
email = ${props.email}
redirect = ${props.redirect}
}
}
locale = Neos.Fusion:Match {
@if.set = ${props.canSendLater}
@subject = ${Carbon.Backend.language()}
@default = 'en'
de = 'de'
}
date = 'now'
sendLater = ${props.canSendLater ? false : null}
pickr = ${props.canSendLater ? '' : null}
formattedDate = ${props.canSendLater ? '' : null}
}
dialogxEffect =${props.canSendLater ? "if(date!='now'){formattedDate=new Date(date).toLocaleDateString(locale,{weekday:'long',year:'numeric',month:'long',day:'numeric',hour:'numeric',minute:'numeric'})}" : null}
confirmLabel = ${this.label}
confirmAttributes = Neos.Fusion:DataStructure {
:href = "action+'&moduleArguments[date]='+date"
}
footer = ${props.footer}
initalFocusOnConfirm = true
type = 'success'
dialogMaxWidth = ${props.canSendLater ? '382px' : null}
content = afx`
<p x-dialog:description @if={!private.hasSegments}>
<span x-show="date=='now'" @if={props.canSendLater}>{props._i18n.id('email.send.now.all').translate()}</span>
<span x-show="date!='now'" @if={props.canSendLater}>{props._i18n.id('email.send.later.all').translate()}</span>
{props.canSendLater ? '' : props._i18n.id('email.send.now.all').translate()}
</p>
<p x-dialog:description @if={private.hasSegments}>
<span x-show="date=='now'" @if={props.canSendLater}>{props._i18n.id('email.send.now.segments').translate()}</span>
<span x-show="date!='now'" @if={props.canSendLater}>{props._i18n.id('email.send.later.segments').translate()}</span>
{props.canSendLater ? '' : props._i18n.id('email.send.now.segments').translate()}
<Neos.Fusion:Loop items={props.segments} @glue=", " content={props.allSegments[item].name} />
</p>
<Garagist.Mautic:Backend.Atom.Checkbox
@if={props.canSendLater}
xModel="sendLater"
boolean={true}
label={props._i18n.id('email.send.later').translate()}
value={true}
/>

<div
@if={props.canSendLater}
x-show="sendLater"
x-collapse
x-effect="date=sendLater&&pickr?pickr:'now'"
>
<label for="date" class="sr-only">{private.sendDateLabel}</label>
<input
x-show="sendLater"
x-model="pickr"
x-flatpickr="{locale}"
placeholder={private.sendDateLabel}
id="date"
class="sr-only"
/>
<!-- Spacer for a smoother animation -->
<div aria-hidden="true" class="h-4"></div>
</div>
`
}
}
95 changes: 95 additions & 0 deletions Resources/Private/Modules/Component/Module/SendTestMail.fusion
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
prototype(Garagist.Mautic:Backend.Module.SendTestMail) < prototype(Neos.Fusion:Component) {
email = null

// Redirect after the action is done
redirect = null

recipients = ${testEmailRecipients}

class = 'neos-button neos-button-primary'

@if.hasEmail = ${this.email}

@private {
i18n = ${Translation.id('').package('Garagist.Mautic').source('Module')}
hasRecipients = ${Carbon.Array.check(props.recipients)}
pushFunc = "{push:()=>{if(!error&&add&&!alreadyDefined){recipients.push(add.toLowerCase());add=''}}}"
}

renderer = Garagist.Mautic:Backend.Block.ActionButton {
label = ${private.i18n.id('email.send.test').translate()}
icon = 'fa-vial'
showText = ${props.detailView}
dialogTitle = ${this.label}
dialogXData = Neos.Fusion:DataStructure {
@process.alpine = ${AlpineJS.object(value)}
action = Neos.Fusion:ActionUri {
action = 'test'
arguments = Neos.Fusion:DataStructure {
node = ${props.node}
email = ${props.email}
redirect = ${props.redirect}
}
}
recipients = ${props.recipients}
error = ''
add = ''
alreadyDefined = false
}
confirmLabel = ${this.label}
confirmAttributes = Neos.Fusion:DataStructure {
:href = "action+'&moduleArguments[recipients]='+recipients"
:class = "!recipients.length&&'pointer-events-none opacity-60'"
}
initalFocusOnConfirm = ${private.hasRecipients}
content = afx`
<div x-show="recipients.length" x-transition>
{private.i18n.id('email.addresses').translate()}
<div class="flex flex-wrap gap-4 mt-2">
<template x-for="(address, index) in recipients">
<div class="flex items-center bg-neos-gray-light">
<div class="py-2 px-4" x-text="address"></div>
<button
x-show="recipients.length>1"
x-on:click="recipients.splice(index, 1);$el.blur()"
class="flex items-center justify-center w-8 h-8 hover:bg-neos-red focus:bg-neos-red focus:outline-none"
aria-label={private.i18n.id('delete').translate()}
x-tooltip="10051"
type="button"
>
<i class="fas fa-times"></i>
</button>
</div>
</template>
</div>
</div>
<label for="add">{private.i18n.id('email.addresses.add').translate()}</label>
<div class="flex !mt-2" x-data={private.pushFunc}>
<input
x-model="add"
x-dialog:focus={!private.hasRecipients}
"x-on:keydown.space.prevent"
"x-on:keydown.enter.prevent"="const msg=$el.validationMessage;if(msg){error=msg}else{push()}"
x-on:change="error=$el.validationMessage"
x-on:input="if(error){error=$el.validationMessage}alreadyDefined=recipients.includes(add.toLowerCase())"
placeholder={private.i18n.id("enter.mailaddress").translate()}
type="email"
id="add"
class="block w-full"
required
/>
<button
x-on:click="push"
aria-label={private.i18n.id('email.addresses.add').translate()}
type="button"
class="neos-button neos-button-secondary focus:outline-none focus:bg-neos-blue"
x-tooltip="10051"
>
<i class="fas fa-plus"></i>
</button>
</div>
<div x-show="error" x-text="error" x-transition class="bg-neos-red text-white p-4"></div>
<div x-show="alreadyDefined" x-transition class="bg-neos-orange text-white p-4">{private.i18n.id('email.addresses.alreadyDefined').translate()}</div>
`
}
}
105 changes: 0 additions & 105 deletions Resources/Private/Modules/Component/SendMail.fusion

This file was deleted.

96 changes: 0 additions & 96 deletions Resources/Private/Modules/Component/SendTestMail.fusion

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
prototype(Garagist.Mautic:Component.EmailData) < prototype(Neos.Fusion:Component) {
prototype(Garagist.Mautic:Backend.Helper.EmailData) < prototype(Neos.Fusion:Component) {
email = null
node = null
redirect = 'node'
15 changes: 15 additions & 0 deletions Resources/Private/Modules/SubViews/Overview.fusion
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
prototype(Garagist.Mautic:BackendController.Overview) < prototype(Neos.Fusion:Component) {
sites = null

renderer = afx`
<main class="mautic">
<Garagist.Mautic:Backend.Block.FlashMessages />
<Garagist.Mautic:Backend.Atom.Logo />
<div @if={Carbon.Array.check(props.sites)} class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<Neos.Fusion:Loop items={props.sites}>
<Garagist.Mautic:Backend.Block.IndexLink {...item} />
</Neos.Fusion:Loop>
</div>
</main>
`
}
64 changes: 64 additions & 0 deletions Resources/Private/Modules/SubViews/Site.fusion
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
prototype(Garagist.Mautic:BackendController.Site) < prototype(Neos.Fusion:Component) {
mauticPublicUrl = null
pint = null
pages = null
categories = null

@private {
i18n = ${Translation.id('').package('Garagist.Mautic').source('Module')}
hasPages = ${Carbon.Array.check(props.pages)}
hasCategories = ${Carbon.Array.check(props.categories)}
showPagesWithoutEmails = ${private.hasPages && Array.some(props.pages, page => page.count > 0) ? 'false' : 'true'}
}

renderer = afx`
<main class="mautic" x-data={AlpineJS.object({showPagesWithoutEmails: private.showPagesWithoutEmails})}>
<Garagist.Mautic:Backend.Block.FlashMessages />
<Garagist.Mautic:Backend.Atom.Logo
url={mauticPublicUrl}
active={ping}
/>
<h2 class="sr-only">{private.i18n.id('newsletter.available').translate()}</h2>
<Garagist.Mautic:Backend.Atom.ToggleSwitch
@if={private.showPagesWithoutEmails == 'false'}
toggle="showPagesWithoutEmails"
label="showPagesWithoutEmails"
/>

<p @if={!private.hasPages} class="my-8">{private.i18n.id('newsletter.none').translate()}</p>

<!-- There are pages with categories -->
<Neos.Fusion:Loop @if={private.hasCategories} items={private.categories}>
<div
class={[iterator.isFirst ? null : "xl:mt-16", "mt-12"]}
x-show={item.main.count || Array.some(item.pages, page => page.count > 0) ? null : 'showPagesWithoutEmails'}
x-transition={!!this['x-show']}
>
<h3 @if={!item.main} class="text-xl mb-4">{item.title}</h3>
<Garagist.Mautic:Backend.Block.IndexLink
@if={item.main}
class="mb-4 text-xl"
show={null}
{...item.main}
/>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<Neos.Fusion:Loop items={item.pages}>
<Garagist.Mautic:Backend.Block.IndexLink {...item} />
</Neos.Fusion:Loop>
</div>
</div>
</Neos.Fusion:Loop>

<!-- There aren't pages with categories -->
<div
@if={!private.hasCategories && private.hasPages}
class="mt-8 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
<Neos.Fusion:Loop items={props.pages}>
<Garagist.Mautic:Backend.Block.IndexLink {...item} />
</Neos.Fusion:Loop>
</div>

</main>
`
}
12 changes: 6 additions & 6 deletions Resources/Private/Modules/Views/Detail.fusion
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ Garagist.Mautic.BackendController.detail = Neos.Fusion:Component {
}
}

emailData = Garagist.Mautic:Component.EmailData {
emailData = Garagist.Mautic:Backend.Helper.EmailData {
node = ${node}
email = ${email}
redirect = 'detail'
@@ -20,7 +20,7 @@ Garagist.Mautic.BackendController.detail = Neos.Fusion:Component {

renderer = afx`
<main class="mautic" x-data x-tooltips>
<Garagist.Mautic:Component.FlashMessages />
<Garagist.Mautic:Backend.Block.FlashMessages />
<div class="space-y-4">
<h1 class="text-2xl">
{props._i18n.id('newsletter.headline').translate()}
@@ -79,7 +79,7 @@ Garagist.Mautic.BackendController.detail = Neos.Fusion:Component {
<td>{props._i18n.id('actions').translate()}</td>
<td class="!p-0" x-data="actions(1)">
<div class="flex flex-col items-start">
<Garagist.Mautic:Component.EmailActionButtons
<Garagist.Mautic:Backend.Module.EmailActionButtons
detailView={true}
{...props.emailData}
/>
@@ -167,22 +167,22 @@ Garagist.Mautic.BackendController.detail = Neos.Fusion:Component {
>
{props._i18n.id('task.unlock').translate()}
</Neos.Fusion:Link.Action>
<Garagist.Mautic:Component.ActionButton
<Garagist.Mautic:Backend.Block.ActionButton
@if={props.action.publish && props.emailData.canPublish}
action="publish"
actionArguments={props.emailData.hrefArguments}
label={props._i18n.id('email.publish').translate()}
footer="primary"
/>
<Garagist.Mautic:Component.SendMail
<Garagist.Mautic:Backend.Module.SendMail
@if={(props.action.send && props.emailData.canSend) || (props.action.resend && props.emailData.canResend)}
action='send'
resend={props.emailData.canResend}
footer="success"
segments={props.emailData.segments}
{...props.emailData.hrefArguments}
/>
<Garagist.Mautic:Component.SendMail
<Garagist.Mautic:Backend.Module.SendMail
@if={props.action.publishAndSend && props.emailData.canPublishAndSend}
action="publishAndSend"
footer="success"
90 changes: 18 additions & 72 deletions Resources/Private/Modules/Views/Index.fusion
Original file line number Diff line number Diff line change
@@ -1,78 +1,24 @@
Garagist.Mautic.BackendController.index = Neos.Fusion:Component {
hasPages = ${Carbon.Array.check(pages)}
hasCategories = ${Carbon.Array.check(categories)}
showPagesWithoutEmails = ${this.hasPages && Array.some(pages, page => page.count > 0) ? 'false' : 'true'}
logo = ${ping ? 'Logo.svg' : 'LogoInactive.svg'}
mauticPublicUrl = ${Configuration.Setting('Garagist.Mautic.publicUrl')}
sites = ${sites}

_i18n = ${Translation.id('').package('Garagist.Mautic').source('Module')}
pages = ${pages}
categories = ${categories}
mauticPublicUrl = ${mauticPublicUrl}
ping = ${ping}

renderer = afx`
<main class="mautic" x-data={"{showPagesWithoutEmails:" + props.showPagesWithoutEmails + "}"}>
<Garagist.Mautic:Component.FlashMessages />
<h1>
<Carbon.Eel:Tag
tagName={props.mauticPublicUrl ? 'a' : null}
attributes.href={props.mauticPublicUrl}
attributes.target="_blank"
attributes.class="inline-block"
>
<span class="sr-only">Mautic</span>
<span
class="inline-block"
aria-label={props._i18n.id(ping ? 'status.active' : 'status.inactive').translate()}
x-tooltip
>
<img
width="152"
alt="Mautic Logo"
aria-hidden="true"
src={StaticResource.uri("Garagist.Mautic", "Public/Assets/" + props.logo)}
/>
</span>
</Carbon.Eel:Tag>
</h1>
<h2 class="sr-only">{props._i18n.id('newsletter.available').translate()}</h2>
<Garagist.Mautic:Component.ToggleSwitch
@if={props.showPagesWithoutEmails == 'false'}
toggle="showPagesWithoutEmails"
label="showPagesWithoutEmails"
/>

<p @if={!props.hasPages} class="my-8">{props._i18n.id('newsletter.none').translate()}</p>

<!-- There are pages with categories -->
<Neos.Fusion:Loop @if={props.hasCategories} items={categories}>
<div
class={[iterator.isFirst ? null : "xl:mt-16", "mt-12"]}
x-show={item.main.count || Array.some(item.pages, page => page.count > 0) ? null : 'showPagesWithoutEmails'}
x-transition={!!this['x-show']}
>
<h3 @if={!item.main} class="text-xl mb-4">{item.title}</h3>
<Garagist.Mautic:Component.IndexLink
@if={item.main}
class="mb-4 text-xl"
show={null}
{...item.main}
/>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<Neos.Fusion:Loop items={item.pages}>
<Garagist.Mautic:Component.IndexLink {...item} />
</Neos.Fusion:Loop>
</div>
</div>
</Neos.Fusion:Loop>
@private.hasSites = ${Carbon.Array.check(props.sites)}

<!-- There aren't pages with categories -->
<div
@if={!props.hasCategories && props.hasPages}
class="mt-8 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
<Neos.Fusion:Loop items={pages}>
<Garagist.Mautic:Component.IndexLink {...item} />
</Neos.Fusion:Loop>
</div>

</main>
renderer = afx`
<Garagist.Mautic:BackendController.Overview
@if={private.hasSites}
sites={props.sites}
/>
<Garagist.Mautic:BackendController.Site
@if={!private.hasSites}
mauticPublicUrl={props.mauticPublicUrl}
ping={props.ping}
pages={props.pages}
categories={props.categories}
/>
`
}
6 changes: 3 additions & 3 deletions Resources/Private/Modules/Views/Node.fusion
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ Garagist.Mautic.BackendController.node = Neos.Fusion:Component {

emails = Neos.Fusion:Map {
items = ${emails}
itemRenderer = Garagist.Mautic:Component.EmailData {
itemRenderer = Garagist.Mautic:Backend.Helper.EmailData {
node = ${node}
email = ${item}
}
@@ -20,7 +20,7 @@ Garagist.Mautic.BackendController.node = Neos.Fusion:Component {

renderer = afx`
<main class="mautic">
<Garagist.Mautic:Component.FlashMessages />
<Garagist.Mautic:Backend.Block.FlashMessages />
<section class="mautic-list">
<h1 class="text-2xl mb-4">
{props._i18n.id('newsletter.headline').translate()}
@@ -103,7 +103,7 @@ Garagist.Mautic.BackendController.node = Neos.Fusion:Component {
x-data="actions(2)"
class="neos-pull-right"
>
<Garagist.Mautic:Component.EmailActionButtons {...item} />
<Garagist.Mautic:Backend.Module.EmailActionButtons {...item} />
<Neos.Fusion:Link.Action
@if={item.properties}
class="neos-button neos-button-primary"
26 changes: 2 additions & 24 deletions composer.json
Original file line number Diff line number Diff line change
@@ -6,12 +6,11 @@
"require": {
"ext-intl": "*",
"ext-json": "*",
"neos/neos-ui": "^7.3 || ^8.0",
"neos/event-sourcing": "^2.0",
"neos/neos": "^7.3 || ^8.0",
"neos/neos": "^8.3",
"neos/fusion-form": "^2.0",
"mautic/api-library": "^3.0.0",
"carbon/eel": "^2.0"
"carbon/eel": "^2.15"
},
"autoload": {
"psr-4": {
@@ -23,27 +22,6 @@
"package-key": "Garagist.Mautic"
}
},
"archive": {
"exclude": [
".github",
".editorconfig",
".eslintignore",
".eslintrc",
".gitattributes",
".gitignore",
".jshintrc",
".nvmrc",
".postcssrc.yml",
".prettierignore",
".prettierrc",
".stylelintrc",
".yarnclean",
"CODE_OF_CONDUCT.md",
"package.json",
"tailwind.config.js",
"yarn.lock"
]
},
"authors": [
{
"name": "David Spiola",