├── Assets └── img │ └── icon.png ├── Config └── config.php ├── Controller ├── Api │ └── MultidomainApiController.php └── MultidomainController.php ├── Entity ├── Multidomain.php └── MultidomainRepository.php ├── Event └── MultidomainEvent.php ├── EventListener ├── BuildJsSubscriber.php ├── BuilderSubscriber.php └── MultidomianSubscriber.php ├── Form └── Type │ └── MultidomainType.php ├── LICENSE ├── MauticMultiDomainBundle.php ├── Model └── MultidomainModel.php ├── Permissions ├── MultidomainPermissions.php └── Security │ └── MultiDomainPermissions.php ├── README.md ├── Translations └── en_US │ ├── messages.ini │ └── validators.ini ├── Views └── Multidomain │ ├── details.html.php │ ├── form.html.php │ ├── index.html.php │ └── list.html.php └── composer.json /Assets/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendlyDotCH/mautic-multi-domain/65db3cdc9c4ecba2242bfa78641558b8757db9c4/Assets/img/icon.png -------------------------------------------------------------------------------- /Config/config.php: -------------------------------------------------------------------------------- 1 | 'Multidomain', 5 | 'description' => 'User can add multiple tracking domains for emails.', 6 | 'author' => 'Abdullah Kiser / Friendly Automate', 7 | 'version' => '1.0.1', 8 | 'routes' => [ 9 | 'main' => [ 10 | 'mautic_multidomain_index' => [ 11 | 'path' => '/multidomain/{page}', 12 | 'controller' => 'MauticMultiDomainBundle:Multidomain:index', 13 | ], 14 | 'mautic_multidomain_action' => [ 15 | 'path' => '/multidomain/{objectAction}/{objectId}', 16 | 'controller' => 'MauticMultiDomainBundle:Multidomain:execute', 17 | ], 18 | ], 19 | 'api' => [ 20 | 'mautic_api_multidomainstandard' => [ 21 | 'standard_entity' => true, 22 | 'name' => 'multidomain', 23 | 'path' => '/multidomain', 24 | 'controller' => 'MauticMultiDomainBundle:Api\MultidomainApi', 25 | ], 26 | ], 27 | ], 28 | 'menu' => [ 29 | 'main' => [ 30 | 'mautic.multidomain.menu' => [ 31 | 'route' => 'mautic_multidomain_index', 32 | 'priority' => 10, 33 | 'iconClass' => 'fa-globe', 34 | ], 35 | ], 36 | ], 37 | 'services' => [ 38 | 'forms' => [ 39 | 'mautic.form.type.multidomain' => [ 40 | 'class' => \MauticPlugin\MauticMultiDomainBundle\Form\Type\MultidomainType::class, 41 | ], 42 | ], 43 | 'models' => [ 44 | 'mautic.multidomain.model.multidomain' => [ 45 | 'class' => \MauticPlugin\MauticMultiDomainBundle\Model\MultidomainModel::class, 46 | 'arguments' => [ 47 | 'mautic.form.model.form', 48 | 'mautic.page.model.trackable', 49 | 'mautic.helper.templating', 50 | 'event_dispatcher', 51 | 'mautic.lead.model.field', 52 | 'mautic.tracker.contact', 53 | 'doctrine.orm.entity_manager', 54 | ], 55 | //'public' => true, 56 | 'alias' => 'model.multidomain.multidomain' 57 | ], 58 | ], 59 | 'events' => [ 60 | 'mautic.multidomain.subscriber.multidomain' => [ 61 | 'class' => \MauticPlugin\MauticMultiDomainBundle\EventListener\MultidomianSubscriber::class, 62 | 'arguments' => [ 63 | 'router', 64 | 'mautic.helper.ip_lookup', 65 | 'mautic.core.model.auditlog', 66 | 'mautic.page.model.trackable', 67 | 'mautic.page.helper.token', 68 | 'mautic.asset.helper.token', 69 | 'mautic.multidomain.model.multidomain', 70 | 'request_stack', 71 | ], 72 | ], 73 | 'mautic.multidomain.subscriber.emailbuilder' => [ 74 | 'class' => \MauticPlugin\MauticMultiDomainBundle\EventListener\BuilderSubscriber::class, 75 | 'arguments' => [ 76 | 'mautic.helper.core_parameters', 77 | 'mautic.email.model.email', 78 | 'mautic.page.model.trackable', 79 | 'mautic.page.model.redirect', 80 | 'translator', 81 | 'doctrine.orm.entity_manager', 82 | 'mautic.multidomain.model.multidomain', 83 | 'router', 84 | ], 85 | ], 86 | 'mautic.multidomain.subscriber.buildjssubscriber' => [ 87 | 'class' => \MauticPlugin\MauticMultiDomainBundle\EventListener\BuildJsSubscriber::class, 88 | 'arguments' => [ 89 | 'templating.helper.assets', 90 | 'request_stack', 91 | 'router', 92 | ], 93 | ], 94 | ], 95 | ], 96 | ]; 97 | -------------------------------------------------------------------------------- /Controller/Api/MultidomainApiController.php: -------------------------------------------------------------------------------- 1 | model = $this->getModel('multidomain'); 20 | $this->entityClass = Multidomain::class; 21 | $this->entityNameOne = 'multidomain'; 22 | $this->entityNameMulti = 'multidomain'; 23 | $this->serializerGroups = ['multidomainDetails', 'publishDetails']; 24 | 25 | parent::initialize($event); 26 | } 27 | } -------------------------------------------------------------------------------- /Controller/MultidomainController.php: -------------------------------------------------------------------------------- 1 | addConstraint(new UniqueEntity([ 42 | 'fields' => 'email', 43 | ])); 44 | $metadata->addPropertyConstraint( 45 | 'email', 46 | new Assert\NotBlank( 47 | [ 48 | 'message' => 'mautic.multidomain.email.required', 49 | ] 50 | ) 51 | ); 52 | 53 | $metadata->addPropertyConstraint( 54 | 'email', 55 | new Assert\Email( 56 | [ 57 | 'message' => 'mautic.multidomain.email.invalid', 58 | ] 59 | ) 60 | ); 61 | 62 | $metadata->addPropertyConstraint( 63 | 'domain', 64 | new Assert\NotBlank( 65 | ['message' => 'mautic.multidomain.domain.required'] 66 | ) 67 | ); 68 | 69 | $metadata->addPropertyConstraint( 70 | 'domain', 71 | new Assert\Url( 72 | ['message' => 'mautic.multidomain.domain.invalid'] 73 | ) 74 | ); 75 | } 76 | 77 | /** 78 | * @param ORM\ClassMetadata $metadata 79 | */ 80 | public static function loadMetadata (ORM\ClassMetadata $metadata) 81 | { 82 | $builder = new ClassMetadataBuilder($metadata); 83 | 84 | $builder->setTable('multi_domain') 85 | ->setCustomRepositoryClass(MultidomainRepository::class); 86 | 87 | // Helper functions 88 | $builder->addId(); 89 | 90 | $builder->createField('email', 'string') 91 | ->columnName('email') 92 | ->build(); 93 | 94 | $builder->createField('domain', 'text') 95 | ->columnName('domain') 96 | ->build(); 97 | 98 | } 99 | 100 | /** 101 | * Prepares the metadata for API usage. 102 | * 103 | * @param $metadata 104 | */ 105 | public static function loadApiMetadata(ApiMetadataDriver $metadata) 106 | { 107 | $metadata->setGroupPrefix('multidomain') 108 | ->addListProperties( 109 | [ 110 | 'id', 111 | 'email', 112 | 'domain', 113 | ] 114 | ) 115 | ->build(); 116 | } 117 | 118 | /** 119 | * @return mixed 120 | */ 121 | public function getId() 122 | { 123 | return $this->id; 124 | } 125 | 126 | public function getEmail(): ?string 127 | { 128 | return $this->email; 129 | } 130 | 131 | public function setEmail(string $email): Multidomain 132 | { 133 | $this->email = $email; 134 | return $this; 135 | } 136 | 137 | public function getDomain(): ?string 138 | { 139 | return $this->domain; 140 | } 141 | 142 | public function setDomain(string $text): Multidomain 143 | { 144 | $this->domain = $text; 145 | return $this; 146 | } 147 | 148 | /** 149 | * Get Fake name to be compatable with getName of commonEntity. 150 | */ 151 | public function getName() 152 | { 153 | return $this->email; 154 | } 155 | 156 | /** 157 | * Set Fake name to be compatable with getName of commonEntity. 158 | * 159 | */ 160 | public function setName(string $email): Multidomain 161 | { 162 | $this->email = $email; 163 | return $this; 164 | } 165 | } -------------------------------------------------------------------------------- /Entity/MultidomainRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('f'); 15 | $q->select('md') 16 | ->from(Multidomain::class, 'md') 17 | ->where('md.isPublished = :isPublished') 18 | ->setParameters(['isPublished' => $isPublished]) 19 | ; 20 | return $q->getQuery()->getResult(); 21 | } 22 | } -------------------------------------------------------------------------------- /Event/MultidomainEvent.php: -------------------------------------------------------------------------------- 1 | entity = $multidomain; 28 | $this->isNew = $isNew; 29 | } 30 | 31 | /** 32 | * Returns the Multidomain entity. 33 | * 34 | * @return MultidomainEvent 35 | */ 36 | public function getMultidomain() 37 | { 38 | return $this->entity; 39 | } 40 | 41 | /** 42 | * Sets the Multidomain entity. 43 | */ 44 | public function setMultidomain(Multidomain $multidomain) 45 | { 46 | $this->entity = $multidomain; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /EventListener/BuildJsSubscriber.php: -------------------------------------------------------------------------------- 1 | assetsHelper = $assetsHelper; 36 | $this->requestStack = $requestStack; 37 | $this->router = $router; 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | public static function getSubscribedEvents() 44 | { 45 | return [ 46 | CoreEvents::BUILD_MAUTIC_JS => [ 47 | ['onBuildJs', 9999], 48 | ], 49 | ]; 50 | } 51 | 52 | public function onBuildJs(BuildJsEvent $event) 53 | { 54 | // PageBundle -> $pageTrackingUrl, $pageTrackingCORSUrl, $contactIdUrl 55 | $context = $this->router->getContext(); 56 | $context->setHost($this->requestStack->getCurrentRequest()->getHttpHost()); 57 | // PageBundle -> $mauticBaseUrl, $jQueryUrl, initGatedVideo 58 | $this->assetsHelper->setSiteUrl($this->requestStack->getCurrentRequest()->getSchemeAndHttpHost()); 59 | 60 | // The only one we can't control now is DynamicContentBundle -> MauticDomain 61 | // however it uses the below, which should mirror where the request was made to - so that's cool. 62 | // $this->requestStack->getCurrentRequest()->getSchemeAndHttpHost(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /EventListener/BuilderSubscriber.php: -------------------------------------------------------------------------------- 1 | coreParametersHelper = $coreParametersHelper; 93 | $this->emailModel = $emailModel; 94 | $this->pageTrackableModel = $trackableModel; 95 | $this->pageRedirectModel = $redirectModel; 96 | $this->translator = $translator; 97 | $this->entityManager = $entityManager; 98 | $this->multidomainModel = $multidomainModel; 99 | $this->router = $router; 100 | } 101 | 102 | /** 103 | * @return array 104 | */ 105 | public static function getSubscribedEvents() 106 | { 107 | return [ 108 | EmailEvents::EMAIL_ON_SEND => [ 109 | ['onEmailGenerate', 0], 110 | // Ensure this is done last in order to catch all tokenized URLs 111 | ['convertUrlsToTokens', -9999], 112 | ], 113 | EmailEvents::EMAIL_ON_DISPLAY => [ 114 | ['onEmailGenerate', -999], 115 | // Ensure this is done last in order to catch all tokenized URLs 116 | ['convertUrlsToTokens', -9999], 117 | ], 118 | ]; 119 | } 120 | 121 | public function onEmailGenerate(EmailSendEvent $event) 122 | { 123 | $idHash = $event->getIdHash(); 124 | $lead = $event->getLead(); 125 | $email = $event->getEmail(); 126 | $senderEmail = null; 127 | $senderDomain = null; 128 | 129 | if(!$email instanceof Email){ 130 | return; 131 | } 132 | 133 | if (null == $idHash) { 134 | // Generate a bogus idHash to prevent errors for routes that may include it 135 | $idHash = uniqid(); 136 | } 137 | 138 | $unsubscribeText = $this->coreParametersHelper->get('unsubscribe_text'); 139 | $webviewText = $this->coreParametersHelper->get('webview_text'); 140 | 141 | // Check if the Mailer is Owner is set, if not then search the sender email from Email channels From address 142 | // And finally if nothing is set, use the mailer from email if it is not blank. 143 | $senderEmail = $this->coreParametersHelper->get("mailer_from_email"); 144 | $mailerIsOwnerGlobal = $this->coreParametersHelper->get('mailer_is_owner'); 145 | $mailerIsOwner = $email->getUseOwnerAsMailer(); 146 | 147 | if(($mailerIsOwnerGlobal || $mailerIsOwner) && $lead['id']){ 148 | $ownerEmail = $event->getTokens()['{ownerfield=email}'] ?? null; 149 | if (!empty($ownerEmail)) { 150 | $senderEmail = $ownerEmail; 151 | } 152 | } 153 | if(empty($ownerEmail) && $email && $email->getFromAddress()){ 154 | $senderEmail = $email->getFromAddress(); 155 | } 156 | 157 | if($senderEmail){ 158 | // Get the Domain from the MultiDomain Configuration by email. 159 | $multiDomain = $this->multidomainModel->getRepository()->findOneBy(['email' => $senderEmail]); 160 | if($multiDomain){ 161 | $senderDomain = $multiDomain->getDomain(); 162 | $messageId = $this->multidomainModel->generateMessageId($multiDomain); 163 | $event->addTextHeader('Message-ID', $messageId); 164 | } 165 | 166 | } 167 | 168 | if (!$unsubscribeText) { 169 | $unsubscribeText = $this->translator->trans('mautic.email.unsubscribe.text', ['%link%' => '|URL|']); 170 | } 171 | $unsubscribeText = str_replace('|URL|', $this->buildUrl('mautic_email_unsubscribe', ['idHash' => $idHash], true, [], [], $senderDomain), $unsubscribeText); 172 | $event->addToken('{unsubscribe_text}', EmojiHelper::toHtml($unsubscribeText)); 173 | 174 | $event->addToken('{unsubscribe_url}', $this->buildUrl('mautic_email_unsubscribe', ['idHash' => $idHash], true, [], [], $senderDomain)); 175 | 176 | 177 | if (!$webviewText) { 178 | $webviewText = $this->translator->trans('mautic.email.webview.text', ['%link%' => '|URL|']); 179 | } 180 | $webviewText = str_replace('|URL|', $this->buildUrl('mautic_email_webview', ['idHash' => $idHash], true, [], [], $senderDomain), $webviewText); 181 | $event->addToken('{webview_text}', EmojiHelper::toHtml($webviewText)); 182 | 183 | // Show public email preview if the lead is not known to prevent 404 184 | if (empty($lead['id']) && $email) { 185 | $event->addToken('{webview_url}', $this->buildUrl('mautic_email_preview', ['objectId' => $email->getId()], true, [], [], $senderDomain)); 186 | } else { 187 | $event->addToken('{webview_url}', $this->buildUrl('mautic_email_webview', ['idHash' => $idHash], true, [], [], $senderDomain)); 188 | } 189 | 190 | $signatureText = $this->coreParametersHelper->get('default_signature_text'); 191 | $fromName = $this->coreParametersHelper->get('mailer_from_name'); 192 | $signatureText = str_replace('|FROM_NAME|', $fromName, nl2br($signatureText)); 193 | $event->addToken('{signature}', EmojiHelper::toHtml($signatureText)); 194 | $event->addToken('{subject}', EmojiHelper::toHtml($event->getSubject())); 195 | } 196 | 197 | /** 198 | * @return array 199 | */ 200 | public function convertUrlsToTokens(EmailSendEvent $event) 201 | { 202 | if ($event->isInternalSend() || $this->coreParametersHelper->get('disable_trackable_urls')) { 203 | // Don't convert urls 204 | return; 205 | } 206 | 207 | $email = $event->getEmail(); 208 | $emailId = ($email) ? $email->getId() : null; 209 | if (!$email instanceof Email) { 210 | $email = $this->emailModel->getEntity($emailId); 211 | } 212 | 213 | $utmTags = $email->getUtmTags(); 214 | $clickthrough = $event->generateClickthrough(); 215 | $trackables = $this->parseContentForUrls($event, $emailId); 216 | 217 | /** 218 | * @var string 219 | * @var Trackable $trackable 220 | */ 221 | foreach ($trackables as $token => $trackable) { 222 | $url = ($trackable instanceof Trackable) 223 | ? 224 | $this->pageTrackableModel->generateTrackableUrl($trackable, $clickthrough, false, $utmTags) 225 | : 226 | $this->pageRedirectModel->generateRedirectUrl($trackable, $clickthrough, false, $utmTags); 227 | 228 | $event->addToken($token, $url); 229 | } 230 | } 231 | 232 | /** 233 | * Parses content for URLs and tokens. 234 | * 235 | * @param $emailId 236 | * 237 | * @return mixed 238 | */ 239 | private function parseContentForUrls(EmailSendEvent $event, $emailId) 240 | { 241 | static $convertedContent = []; 242 | 243 | // Prevent parsing the exact same content over and over 244 | if (!isset($convertedContent[$event->getContentHash()])) { 245 | $html = $event->getContent(); 246 | $text = $event->getPlainText(); 247 | 248 | $contentTokens = $event->getTokens(); 249 | 250 | [$content, $trackables] = $this->pageTrackableModel->parseContentForTrackables( 251 | [$html, $text], 252 | $contentTokens, 253 | ($emailId) ? 'email' : null, 254 | $emailId 255 | ); 256 | 257 | [$html, $text] = $content; 258 | unset($content); 259 | 260 | if ($html) { 261 | $event->setContent($html); 262 | } 263 | if ($text) { 264 | $event->setPlainText($text); 265 | } 266 | 267 | $convertedContent[$event->getContentHash()] = $trackables; 268 | 269 | // Don't need to preserve Trackable or Redirect entities in memory 270 | $this->entityManager->clear(Redirect::class); 271 | $this->entityManager->clear(Trackable::class); 272 | 273 | unset($html, $text, $trackables); 274 | } 275 | 276 | return $convertedContent[$event->getContentHash()]; 277 | } 278 | 279 | private function buildUrl( 280 | $route, 281 | $routeParams = [], 282 | $absolute = true, 283 | $clickthrough = [], 284 | $utmTags = [], 285 | $domain = null 286 | ) 287 | { 288 | 289 | 290 | if($domain){ 291 | $parseUrl = parse_url($domain); 292 | $context = $this->router->getGenerator()->getContext(); 293 | $context->setHost($parseUrl['host']); 294 | $context->setScheme($parseUrl['scheme']); 295 | } 296 | 297 | $referenceType = ($absolute) ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH; 298 | $url = $this->router->generate($route, $routeParams, $referenceType); 299 | 300 | return $url.((!empty($clickthrough)) ? '?ct='.$this->encodeArrayForUrl($clickthrough) : ''); 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /EventListener/MultidomianSubscriber.php: -------------------------------------------------------------------------------- 1 | router = $router; 86 | $this->ipHelper = $ipLookupHelper; 87 | $this->auditLogModel = $auditLogModel; 88 | $this->trackableModel = $trackableModel; 89 | $this->pageTokenHelper = $pageTokenHelper; 90 | $this->assetTokenHelper = $assetTokenHelper; 91 | $this->multidomainModel = $multidomainModel; 92 | $this->requestStack = $requestStack; 93 | } 94 | 95 | /** 96 | * {@inheritdoc} 97 | */ 98 | public static function getSubscribedEvents() 99 | { 100 | return [ 101 | KernelEvents::REQUEST => ['onKernelRequest', 0], 102 | 'mautic.multidomain_post_save' => ['onMultidomainPostSave', 0], 103 | 'mautic.multidomain_delete' => ['onMultidomainDelete', 0], 104 | 'mautic.multidomain_replacement' => ['onTokenReplacement', 0], 105 | ]; 106 | } 107 | 108 | /* 109 | * Check and hijack the form's generate link if the ID has mf- in it 110 | */ 111 | public function onKernelRequest(GetResponseEvent $event) 112 | { 113 | if ($event->isMasterRequest()) { 114 | // get the current event request 115 | $request = $event->getRequest(); 116 | $requestUri = $request->getRequestUri(); 117 | 118 | $formGenerateUrl = $this->router->generate('mautic_form_generateform'); 119 | 120 | if (false !== strpos($requestUri, $formGenerateUrl)) { 121 | $id = InputHelper::_($this->requestStack->getCurrentRequest()->get('id')); 122 | if (0 === strpos($id, 'mf-')) { 123 | $mfId = str_replace('mf-', '', $id); 124 | $multidomainGenerateUrl = $this->router->generate('mautic_multidomain_action', ['id' => $mfId]); 125 | 126 | $event->setResponse(new RedirectResponse($multidomainGenerateUrl)); 127 | } 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * Add an entry to the audit log. 134 | */ 135 | public function onMultidomainPostSave(MultidomainEvent $event) 136 | { 137 | $entity = $event->getMultidomain(); 138 | if ($details = $event->getChanges()) { 139 | $log = [ 140 | 'bundle' => 'multidomain', 141 | 'object' => 'multidomain', 142 | 'objectId' => $entity->getId(), 143 | 'action' => ($event->isNew()) ? 'create' : 'update', 144 | 'details' => $details, 145 | 'ipAddress' => $this->ipHelper->getIpAddressFromRequest(), 146 | ]; 147 | 148 | $this->auditLogModel->writeToLog($log); 149 | 150 | } 151 | } 152 | 153 | /** 154 | * Add a delete entry to the audit log. 155 | */ 156 | public function onMultidomainDelete(MultidomainEvent $event) 157 | { 158 | $entity = $event->getMultidomain(); 159 | $log = [ 160 | 'bundle' => 'multidomain', 161 | 'object' => 'multidomain', 162 | 'objectId' => $entity->deletedId, 163 | 'action' => 'delete', 164 | 'details' => ['name' => $entity->getName()], 165 | 'ipAddress' => $this->ipHelper->getIpAddressFromRequest(), 166 | ]; 167 | $this->auditLogModel->writeToLog($log); 168 | } 169 | 170 | public function onTokenReplacement(MauticEvents\TokenReplacementEvent $event) 171 | { 172 | /** @var Lead $lead */ 173 | $lead = $event->getLead(); 174 | $content = $event->getContent(); 175 | $clickthrough = $event->getClickthrough(); 176 | 177 | if ($content) { 178 | $tokens = array_merge( 179 | $this->pageTokenHelper->findPageTokens($content, $clickthrough), 180 | $this->assetTokenHelper->findAssetTokens($content, $clickthrough) 181 | ); 182 | 183 | if ($lead && $lead->getId()) { 184 | $tokens = array_merge($tokens, TokenHelper::findLeadTokens($content, $lead->getProfileFields())); 185 | } 186 | 187 | list($content, $trackables) = $this->trackableModel->parseContentForTrackables( 188 | $content, 189 | $tokens, 190 | 'multidomain', 191 | $clickthrough['multidomain_id'] 192 | ); 193 | 194 | $multidomain = $this->multidomainModel->getEntity($clickthrough['multidomain_id']); 195 | 196 | /** 197 | * @var string 198 | * @var Trackable $trackable 199 | */ 200 | foreach ($trackables as $token => $trackable) { 201 | $tokens[$token] = $this->trackableModel->generateTrackableUrl($trackable, $clickthrough, false, $multidomain->getUtmTags()); 202 | } 203 | 204 | $content = str_replace(array_keys($tokens), array_values($tokens), $content); 205 | 206 | $event->setContent($content); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /Form/Type/MultidomainType.php: -------------------------------------------------------------------------------- 1 | add('email', TextType::class, [ 21 | 'label' => 'plugin.multidomain.email', 22 | 'required' => true, 23 | 'attr' => ['class' => 'form-control'] 24 | ]) 25 | ->add('domain', TextType::class, [ 26 | 'label' => 'plugin.multidomain.domain', 27 | 'required' => true, 28 | 'attr' => ['class' => 'form-control'] 29 | ]) 30 | ; 31 | 32 | $builder->add( 33 | 'buttons', 34 | FormButtonsType::class 35 | ); 36 | 37 | if (!empty($options['action'])) { 38 | $builder->setAction($options['action']); 39 | } 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function getBlockPrefix() 46 | { 47 | return 'multidomain_type'; 48 | } 49 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Friendly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MauticMultiDomainBundle.php: -------------------------------------------------------------------------------- 1 | getEntityManager()); 18 | } 19 | 20 | if (null !== $metadata) { 21 | parent::onPluginInstall($plugin, $factory, $metadata, $installedSchema); 22 | } 23 | } 24 | 25 | /** 26 | * Fix: plugin installer doesn't find metadata entities for the plugin 27 | * PluginBundle/Controller/PluginController:410. 28 | * 29 | * @return array|null 30 | */ 31 | private static function getMetadata(EntityManager $em) 32 | { 33 | $allMetadata = $em->getMetadataFactory()->getAllMetadata(); 34 | $currentSchema = $em->getConnection()->getSchemaManager()->createSchema(); 35 | 36 | $classes = []; 37 | 38 | /** @var \Doctrine\ORM\Mapping\ClassMetadata $meta */ 39 | foreach ($allMetadata as $meta) { 40 | if (false === strpos($meta->namespace, 'MauticPlugin\\MauticMultiDomainBundle')) { 41 | continue; 42 | } 43 | 44 | $table = $meta->getTableName(); 45 | 46 | if ($currentSchema->hasTable($table)) { 47 | continue; 48 | } 49 | 50 | $classes[] = $meta; 51 | } 52 | 53 | return $classes ?: null; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Model/MultidomainModel.php: -------------------------------------------------------------------------------- 1 | formModel = $formModel; 86 | $this->trackableModel = $trackableModel; 87 | $this->templating = $templating; 88 | $this->dispatcher = $dispatcher; 89 | $this->leadFieldModel = $leadFieldModel; 90 | $this->contactTracker = $contactTracker; 91 | static::$entityManager = $entityManager; 92 | } 93 | 94 | /** 95 | * @return string 96 | */ 97 | public function getActionRouteBase() 98 | { 99 | return 'multidomain'; 100 | } 101 | 102 | /** 103 | * @return string 104 | */ 105 | public function getPermissionBase() 106 | { 107 | return 'multidomain:items'; 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | * 113 | * @param object $entity 114 | * @param \Symfony\Component\Form\FormFactory $formFactory 115 | * @param null $action 116 | * @param array $options 117 | * 118 | * @throws NotFoundHttpException 119 | */ 120 | public function createForm($entity, $formFactory, $action = null, $options = []) 121 | { 122 | if (!$entity instanceof Multidomain) { 123 | throw new MethodNotAllowedHttpException(['Multidomain']); 124 | } 125 | 126 | if (!empty($action)) { 127 | $options['action'] = $action; 128 | } 129 | 130 | return $formFactory->create(MultidomainType::class, $entity, $options); 131 | } 132 | 133 | /** 134 | * {@inheritdoc} 135 | * 136 | * @return \MauticPlugin\MauticMultiDomainBundle\Entity\MultidomainRepository 137 | */ 138 | public function getRepository() 139 | { 140 | return $this->em->getRepository(Multidomain::class); 141 | } 142 | 143 | /** 144 | * {@inheritdoc} 145 | * 146 | * @param null $id 147 | * 148 | * @return Multidomain 149 | */ 150 | public function getEntity($id = null) 151 | { 152 | if (null === $id) { 153 | return new Multidomain(); 154 | } 155 | 156 | return parent::getEntity($id); 157 | } 158 | 159 | /** 160 | * {@inheritdoc} 161 | * 162 | * @param Multidomain $entity 163 | * @param bool|false $unlock 164 | */ 165 | public function saveEntity($entity, $unlock = true) 166 | { 167 | parent::saveEntity($entity, $unlock); 168 | $this->getRepository()->saveEntity($entity); 169 | } 170 | 171 | public function generateMessageId(Multidomain $multidomain) { 172 | $url = $multidomain->getDomain(); 173 | $parts = parse_url($url); 174 | if (!isset($parts['host'])) { 175 | throw new \Exception("InvalidDomainError"); 176 | } 177 | 178 | $messageIdSuffix = '@' . $parts['host']; 179 | return bin2hex(random_bytes(16)).$messageIdSuffix; 180 | } 181 | 182 | 183 | /** 184 | * Get whether the color is light or dark. 185 | * 186 | * @param $hex 187 | * @param $level 188 | * 189 | * @return bool 190 | */ 191 | public static function isLightColor($hex, $level = 200) 192 | { 193 | $hex = str_replace('#', '', $hex); 194 | $r = hexdec(substr($hex, 0, 2)); 195 | $g = hexdec(substr($hex, 2, 2)); 196 | $b = hexdec(substr($hex, 4, 2)); 197 | 198 | $compareWith = ((($r * 299) + ($g * 587) + ($b * 114)) / 1000); 199 | 200 | return $compareWith >= $level; 201 | } 202 | 203 | /** 204 | * {@inheritdoc} 205 | * 206 | * @return bool|MultidomainEvent|void 207 | * 208 | * @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException 209 | */ 210 | protected function dispatchEvent($action, &$entity, $isNew = false, Event $event = null) 211 | { 212 | if (!$entity instanceof Multidomain) { 213 | throw new MethodNotAllowedHttpException(['Multidomain']); 214 | } 215 | 216 | switch ($action) { 217 | case 'pre_save': 218 | $name = 'mautic.multidomain_pre_save'; 219 | break; 220 | case 'post_save': 221 | $name = 'mautic.multidomain_post_save'; 222 | break; 223 | case 'pre_delete': 224 | $name = 'mautic.multidomain_pre_delete'; 225 | break; 226 | case 'post_delete': 227 | $name = 'mautic.multidomain_post_delete'; 228 | break; 229 | default: 230 | return null; 231 | } 232 | 233 | if ($this->dispatcher->hasListeners($name)) { 234 | if (empty($event)) { 235 | $event = new MultidomainEvent($entity, $isNew); 236 | $event->setEntityManager($this->em); 237 | } 238 | 239 | $this->dispatcher->dispatch($name, $event); 240 | 241 | return $event; 242 | } else { 243 | return null; 244 | } 245 | } 246 | 247 | // Get path of the config.php file. 248 | public function getConfiArray() 249 | { 250 | return include dirname(__DIR__).'/Config/config.php'; 251 | } 252 | 253 | } 254 | -------------------------------------------------------------------------------- /Permissions/MultidomainPermissions.php: -------------------------------------------------------------------------------- 1 | addStandardPermissions('categories'); 29 | $this->addExtendedPermissions('items'); 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | * 35 | * @return string|void 36 | */ 37 | public function getName() 38 | { 39 | return 'multiDomain'; 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function buildForm(FormBuilderInterface &$builder, array $options, array $data) 46 | { 47 | $this->addStandardFormFields('multiDomain', 'categories', $builder, $data); 48 | $this->addExtendedFormFields('multiDomain', 'items', $builder, $data); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Permissions/Security/MultiDomainPermissions.php: -------------------------------------------------------------------------------- 1 | addStandardPermissions('categories'); 29 | $this->addExtendedPermissions('items'); 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | * 35 | * @return string|void 36 | */ 37 | public function getName() 38 | { 39 | return 'multiDomain'; 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function buildForm(FormBuilderInterface &$builder, array $options, array $data) 46 | { 47 | $this->addStandardFormFields('multiDomain', 'categories', $builder, $data); 48 | $this->addExtendedFormFields('multiDomain', 'items', $builder, $data); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mautic Multi Domain Plugin creates the possibility to add tracking domains to Mautic 2 | 3 | ```shell 4 | @@@@@@@@@@@BGP55555555PGB#@@@@@@@@@@@@ 5 | @@@@@@@@#BP55PGGB######BGGP5#@@@@@@@@@@@ 6 | @@@@@&BP55G#&@@@@@@@@@@@@@@@@@@@@@@@@@@@ 7 | @@@@B55P#@@@@@@@@@@@@@@@@@@@#BGP5#@@@@@@ 8 | @@&P55#@@@@@@@@@@@@@@@@@@@@#7~~~~#@@@@@@ 9 | @&P5P&@@@@@@GP@@@@@@@@@@@&5!~~~!J@@@@&@@ 10 | @P5P&@@@@@@&!~7P&@@@@@@&5!~~~JB&&@@@G5P& 11 | B55#@@@@@@@5~~~~7P&@@&5!~~~?B@@@@@@@#55G 12 | P5P@@@@@@@&!~~~~~~7PP!~~~?B@B@@@@@@@@P55 13 | 55G@@@@@@@5~~~PB?~~~~~~?G@G7~B@@@@@@@G55 14 | P5P@@@@@@&!~~7@@@G7~~7G@@#~~~?@@@@@@@P55 15 | B55#@@@@@5~~~P@@@@@PP@@@@@Y~~~B@@@@@#55G 16 | @P5P&@@@&!~~7@@@@@@@@@@@@@#~~~7@@@@@P5P& 17 | @&P5P&@@#555B@@@@@@@@@@@@@@G555&@@&P55&@ 18 | @@&P55#@@@@@@@@@@@@@@@@@@@@@@@@@@#55P&@@ 19 | @@@@B55P#@@@@@@@@@@@@@@@@@@@@@@#P55B@@@@ 20 | @@@@@&BP55G#&@@@@@@@@@@@@@@G55PB&@@@@@ 21 | @@@@@@@@#BP55PPGB######BGGP55PB#@@@@@@@@ 22 | @@@@@@@@@@@BGP55555555PGB#&@@@@@@@B#GG 23 | >>>>>>>>>>>>> JOS OF BHS <<<<<<<<<<<<<<< 24 | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 25 | 26 | # * ( ( ( ) * ( ) 27 | # ( ` )\ ) * ))\ ) )\ ) ( /( ( ` ( )\ ) ( /( 28 | # )\))( ( (()/(` ) /(()/( (()/( )\()) )\))( )\ (()/( )\()) 29 | # ((_)()\ )\ /(_))( )(_))(_)) /(_))((_)\ ((_)()((((_)( /(_)|(_)\ 30 | # (_()((_)_ ((_|_)) (_(_()|_)) (_))_ ((_)(_()((_)\ _ )\(_)) _((_) 31 | # | \/ | | | | | |_ _|_ _| | \ / _ \| \/ (_)_\(_)_ _|| \| | 32 | # | |\/| | |_| | |__ | | | | | |) | (_) | |\/| |/ _ \ | | | .` | 33 | # |_| |_|\___/|____| |_| |___| |___/ \___/|_| |_/_/ \_\|___||_|\_| 34 | # * ( 35 | # ( ( ` ( * ))\ ) ( 36 | # )\ ) ( )\))( )\ ( ` ) /(()/( )\ 37 | # (()/( ( )( ((_)()((((_)( )\ ( )(_))(_)|((_) 38 | # /(_)) )\(()\ (_()((_)\ _ )\ _ ((_|_(_()|_)) )\___ 39 | # (_) _|((_)((_) | \/ (_)_\(_) | | |_ _|_ _((/ __| 40 | # | _/ _ \ '_| | |\/| |/ _ \ | |_| | | | | | | (__ 41 | # |_| \___/_| |_| |_/_/ \_\ \___/ |_| |___| \___| 42 | # 43 | 44 | ``` 45 | 46 | # Key features 47 | 48 | - Replaces the tracking domain in your emails based on the sender email address. 49 | - The plugin replaces list unsubscribe, image pixel, webview and unsubscribe tokens. 50 | - This plugin also rewrites all the tracking javascript domains to the domain from the http request 51 | if your Mautic domain is https://mautic.example.com and tracking js is loaded from from a CNAME, https://trk.example.net all of the domains used inside the tracking javascript will be `trk.example.net` (rather than `mautic.example.com`) to avoid the appearance of third-paty requests. 52 | 53 | # What does it do and why you need it: 54 | https://www.youtube.com/watch?v=O8_pcHMXV-M 55 | 56 | 57 | # Installation 58 | 59 | ```shell 60 | # add the plugin to your project composer.json 61 | composer require icecubenz/mauticmultidomain 62 | # clear cache 63 | php bin/console cache:clear --env=prod # or delete var/cache/prod/* 64 | ``` 65 | 66 | ## Manual Installation 67 | 68 | * Upload the zip package to your `plugins/` 69 | * Unzip 70 | * Rename the plugin folder to `MauticMultiDomainBundle` 71 | * Refresh plugins 72 | 73 | # API 74 | in version 1.3 Amazing API upgrade from 75 | https://github.com/MPCKowalski and https://github.com/cubitt0 76 | 77 | REST API capability allows you the follwoings: 78 | 79 | - Get info of a domain: 80 | `GET /multidomain/ID` 81 | - List all multidomain entries: 82 | `GET /multidomain` 83 | - Create new multidomain entry: 84 | `POST /multidomain/new` 85 | body parameters: email, domain 86 | - Edit an existing multidomain entry: 87 | `PUT /multidomain/ID/edit` 88 | `PATCH /multidomain/ID/edit` 89 | body parameters: email, domain 90 | - Delete Multidomain entry: 91 | `DEL /multidomain/ID/delete` 92 | 93 | # Permissions 94 | Thanks to surge.media since release 1.4 the plugin obeys the Mauic permissions system. 95 | New roles can be set. 96 | 97 | # Next steps 98 | 99 | - Create option to change URL for images as well. 100 | - Better how to guides 101 | 102 | # Credits 103 | 104 | Original work: https://github.com/friendly-ch/mautic-multi-domain 105 | Amazing upgrade done by: https://github.com/rjocoleman 106 | -------------------------------------------------------------------------------- /Translations/en_US/messages.ini: -------------------------------------------------------------------------------- 1 | plugin.multidomain.email = "Email" 2 | plugin.multidomain.domain = "Domain" 3 | mautic.multidomain.new = "Add New MultiDomain" 4 | mautic.multidomain.edit = "Edit MultiDomain" 5 | mautic.multidomain.menu = "Multi Domain" 6 | mautic.multidomain.title = "Multi Domain list" 7 | mautic.multidomain.form.confirmdelete = "Do you want to delete the Multi Domain item, %name%?" 8 | mautic.multidomain.add.warning = "Please specify the sender email address, which will use the assigned tracking domain. Make sure, your tracking domain has a CNAME entry for your mautic URL. For example: CNAME subdomain.mauticdomain.com subdomain.trackingdomain.com." 9 | mautic.multiDomain.permissions.header = "Multi Domain Permissions" 10 | mautic.multiDomain.permissions.items = "MultiDomain - User Has Access" 11 | -------------------------------------------------------------------------------- /Translations/en_US/validators.ini: -------------------------------------------------------------------------------- 1 | mautic.multidomain.email.required = "Email is required" 2 | mautic.multidomain.email.invalid = "Invalid Email address" 3 | mautic.multidomain.domain.required = "Domain is required" 4 | mautic.multidomain.domain.invalid = "Invalid Domain" -------------------------------------------------------------------------------- /Views/Multidomain/details.html.php: -------------------------------------------------------------------------------- 1 | extend('MauticCoreBundle:Default:content.html.php'); 4 | $view['slots']->set('mauticContent', 'multidomain'); 5 | $view['slots']->set('headerTitle', $item->getEmail()); 6 | 7 | $view['slots']->set( 8 | 'actions', 9 | $view->render( 10 | 'MauticCoreBundle:Helper:page_actions.html.php', 11 | [ 12 | 'item' => $item, 13 | 'templateButtons' => [ 14 | 'edit' => $view['security']->hasEntityAccess( 15 | $permissions['multidomain:items:editown'], 16 | $permissions['multidomain:items:editother'], 17 | $item->getCreatedBy() 18 | ), 19 | 'clone' => $permissions['multidomain:items:create'], 20 | 'delete' => $view['security']->hasEntityAccess( 21 | $permissions['multidomain:items:deleteown'], 22 | $permissions['multidomain:items:deleteother'], 23 | $item->getCreatedBy() 24 | ), 25 | 'close' => $view['security']->isGranted('multidomain:items:view'), 26 | ], 27 | 'routeBase' => 'multidomain', 28 | 'langVar' => 'multidomain', 29 | ] 30 | ) 31 | ); 32 | 33 | ?> 34 | 35 | 36 |
getDomain();?>
55 |28 | trans('mautic.multidomain.add.warning'); ?> 29 |
30 |51 | render( 53 | 'MauticCoreBundle:Helper:list_actions.html.php', 54 | [ 55 | 'item' => $item, 56 | 'templateButtons' => [ 57 | 'edit' => $view['security']->hasEntityAccess( 58 | $permissions['multidomain:items:editown'], 59 | $permissions['multidomain:items:editother'], 60 | $item->getCreatedBy() 61 | ), 62 | 'clone' => $permissions['multidomain:items:create'], 63 | 'delete' => $view['security']->hasEntityAccess( 64 | $permissions['multidomain:items:deleteown'], 65 | $permissions['multidomain:items:deleteother'], 66 | $item->getCreatedBy() 67 | ), 68 | ], 69 | 'routeBase' => 'multidomain', 70 | ] 71 | ); 72 | ?> 73 | | 74 |
75 |
76 |
77 |
81 | getEmail(); ?>
82 |
83 |
84 | getDomain()): ?>
85 |
86 |
87 |
88 |
89 | |
90 |