vendor/pimcore/pimcore/lib/Tool/Newsletter.php line 293

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Enterprise License (PEL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  * @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  * @license    http://www.pimcore.org/license     GPLv3 and PEL
  13.  */
  14. namespace Pimcore\Tool;
  15. use Exception;
  16. use InvalidArgumentException;
  17. use Pimcore;
  18. use Pimcore\Config;
  19. use Pimcore\Document\Newsletter\SendingParamContainer;
  20. use Pimcore\Event\DocumentEvents;
  21. use Pimcore\File;
  22. use Pimcore\Logger;
  23. use Pimcore\Mail;
  24. use Pimcore\Model;
  25. use Pimcore\Model\DataObject;
  26. use Pimcore\Model\Document;
  27. use Pimcore\Tool;
  28. use Symfony\Component\EventDispatcher\GenericEvent;
  29. class Newsletter
  30. {
  31.     public const SENDING_MODE_BATCH 'batch';
  32.     public const SENDING_MODE_SINGLE 'single';
  33.     /**
  34.      * @var DataObject\ClassDefinition
  35.      */
  36.     protected $class;
  37.     /**
  38.      * @param Document\Newsletter $newsletterDocument
  39.      * @param SendingParamContainer|null $sendingContainer
  40.      * @param string|null $hostUrl
  41.      *
  42.      * @return Mail
  43.      *
  44.      * @throws Exception
  45.      */
  46.     public static function prepareMail(
  47.         Document\Newsletter $newsletterDocument,
  48.         SendingParamContainer $sendingContainer null,
  49.         $hostUrl null
  50.     ): Mail {
  51.         $mail = new Mail();
  52.         $mail->setIgnoreDebugMode(true);
  53.         $config Config::getSystemConfiguration('newsletter');
  54.         if ($config['use_specific']) {
  55.             $mail->init('newsletter');
  56.         }
  57.         if ($hostUrl) {
  58.             $mail->setHostUrl($hostUrl);
  59.         }
  60.         $mail->setDocument($newsletterDocument);
  61.         if ($sendingContainer && $sendingContainer->getParams()) {
  62.             $mail->setParams($sendingContainer->getParams());
  63.         }
  64.         if (strlen(trim($newsletterDocument->getPlaintext())) > 0) {
  65.             $mail->setBodyText(trim($newsletterDocument->getPlaintext()));
  66.         }
  67.         $contentHTML $mail->getBodyHtmlRendered();
  68.         $contentText $mail->getBodyTextRendered();
  69.         // render the document and rewrite the links (if analytics is enabled)
  70.         if ($contentHTML && $newsletterDocument->getEnableTrackingParameters()) {
  71.             $html str_get_html($contentHTML);
  72.             if ($html) {
  73.                 $links $html->find('a');
  74.                 foreach ($links as $link) {
  75.                     if (preg_match('/^(mailto|#)/i'trim($link->href))) {
  76.                         // No tracking for mailto and hash only links
  77.                         continue;
  78.                     }
  79.                     $urlParts parse_url($link->href);
  80.                     $glue '?';
  81.                     $params sprintf(
  82.                         'utm_source=%s&utm_medium=%s&utm_campaign=%s',
  83.                         $newsletterDocument->getTrackingParameterSource(),
  84.                         $newsletterDocument->getTrackingParameterMedium(),
  85.                         $newsletterDocument->getTrackingParameterName()
  86.                     );
  87.                     if (isset($urlParts['query'])) {
  88.                         $glue '&';
  89.                     }
  90.                     $link->href preg_replace('/[#].+$/'''$link->href).$glue.$params;
  91.                     if (isset($urlParts['fragment'])) {
  92.                         $link->href .= '#'.$urlParts['fragment'];
  93.                     }
  94.                 }
  95.                 $contentHTML $html->save();
  96.                 $html->clear();
  97.                 unset($html);
  98.             }
  99.             $mail->setBodyHtml($contentHTML);
  100.         }
  101.         $mail->setBodyHtml($contentHTML);
  102.         $mail->setBodyText($contentText);
  103.         // Adds the plain text part to the message, that it becomes a multipart email
  104.         $mail->addPart($contentText'text/plain');
  105.         $mail->setSubject($mail->getSubjectRendered());
  106.         return $mail;
  107.     }
  108.     /**
  109.      * @param Mail $mail
  110.      * @param SendingParamContainer $sendingContainer
  111.      *
  112.      * @throws Exception
  113.      */
  114.     public static function sendNewsletterDocumentBasedMail(Mail $mailSendingParamContainer $sendingContainer): void
  115.     {
  116.         $mailAddress $sendingContainer->getEmail();
  117.         $config Config::getSystemConfiguration('newsletter');
  118.         if (!self::to_domain_exists($mailAddress)) {
  119.             Logger::err('E-Mail address invalid: ' self::obfuscateEmail($mailAddress));
  120.             $mailAddress null;
  121.         }
  122.         if (!empty($mailAddress)) {
  123.             $mail->setTo($mailAddress);
  124.             $mailer null;
  125.             // check if newsletter specific mailer is needed
  126.             if ($config['use_specific']) {
  127.                 $mailer Pimcore::getContainer()->get('swiftmailer.mailer.newsletter_mailer');
  128.             }
  129.             $event = new GenericEvent($mail, [
  130.                 'mail' => $mail,
  131.                 'document' => $mail->getDocument(),
  132.                 'sendingContainer' => $sendingContainer,
  133.                 'mailer' => $mailer,
  134.             ]);
  135.             Pimcore::getEventDispatcher()->dispatch(DocumentEvents::NEWSLETTER_PRE_SEND$event);
  136.             $mail->sendWithoutRendering($mailer);
  137.             Pimcore::getEventDispatcher()->dispatch(DocumentEvents::NEWSLETTER_POST_SEND$event);
  138.             Logger::info(
  139.                 sprintf(
  140.                     'Sent newsletter to: %s [%s]',
  141.                     self::obfuscateEmail($mailAddress),
  142.                     $mail->getDocument() ? $mail->getDocument()->getId() : null
  143.                 )
  144.             );
  145.         } else {
  146.             Logger::warn(
  147.                 sprintf(
  148.                     'No E-Mail Address given - cannot send mail. [%s]',
  149.                     $mail->getDocument() ? $mail->getDocument()->getId() : null
  150.                 )
  151.             );
  152.         }
  153.     }
  154.     /**
  155.      * @param string $email
  156.      *
  157.      * @return string
  158.      */
  159.     protected static function obfuscateEmail($email)
  160.     {
  161.         $email substr_replace($email'.xxx'strrpos($email'.'));
  162.         return $email;
  163.     }
  164.     /**
  165.      * @param Model\Document\Newsletter $newsletter
  166.      * @param DataObject\Concrete $object
  167.      * @param string|null $emailAddress
  168.      * @param string|null $hostUrl
  169.      *
  170.      * @throws Exception
  171.      *
  172.      * @deprecated Pimcore\Tool\Newsletter::sendMail is deprecated and will be removed with 7.0,
  173.      * please use the internal:newsletter-document-send command instead.
  174.      */
  175.     public static function sendMail($newsletter$object$emailAddress null$hostUrl null): void
  176.     {
  177.         trigger_error(
  178.             sprintf(
  179.                 '%s::%s is deprecated and will be removed with 7.0, please use the %s command instead.',
  180.                 static::class,
  181.                 __METHOD__,
  182.                 'internal:newsletter-document-send'
  183.             ),
  184.             E_USER_DEPRECATED
  185.         );
  186.         $config Config::getSystemConfiguration('newsletter');
  187.         $params = [
  188.             'gender' => $object->getGender(),
  189.             'firstname' => $object->getFirstname(),
  190.             'lastname' => $object->getLastname(),
  191.             'email' => $object->getEmail(),
  192.             'token' => $object->getProperty('token'),
  193.             'object' => $object,
  194.         ];
  195.         $mail = new Mail();
  196.         $mail->setIgnoreDebugMode(true);
  197.         if ($config['use_specific']) {
  198.             $mail->init('newsletter');
  199.         }
  200.         if ($hostUrl) {
  201.             $mail->setHostUrl($hostUrl);
  202.         }
  203.         if ($emailAddress) {
  204.             $mail->addTo($emailAddress);
  205.         } else {
  206.             $mail->addTo($object->getEmail());
  207.         }
  208.         $mail->setDocument(Document::getById($newsletter->getDocument()));
  209.         $mail->setParams($params);
  210.         // render the document and rewrite the links (if analytics is enabled)
  211.         if ($newsletter->getGoogleAnalytics() && $content $mail->getBodyHtmlRendered()) {
  212.             $html str_get_html($content);
  213.             if ($html) {
  214.                 $links $html->find('a');
  215.                 foreach ($links as $link) {
  216.                     if (preg_match('/^(mailto)/i'trim($link->href))) {
  217.                         continue;
  218.                     }
  219.                     $glue strpos($link->href'?') ? '&' '?';
  220.                     $link->href .= sprintf(
  221.                         '%sutm_source=Newsletter&utm_medium=Email&utm_campaign=%s',
  222.                         $glue,
  223.                         $newsletter->getName()
  224.                     );
  225.                 }
  226.                 $content $html->save();
  227.                 $html->clear();
  228.                 unset($html);
  229.             }
  230.             $mail->setBodyHtml($content);
  231.         }
  232.         $mail->send();
  233.     }
  234.     /**
  235.      * @param string $classId
  236.      *
  237.      * @throws Exception
  238.      */
  239.     public function __construct($classId)
  240.     {
  241.         $class null;
  242.         if (is_numeric($classId)) {
  243.             $class DataObject\ClassDefinition::getById($classId);
  244.         } else {
  245.             $class DataObject\ClassDefinition::getByName($classId);
  246.         }
  247.         if (!$class) {
  248.             throw new InvalidArgumentException('No valid class identifier given (class name or ID)');
  249.         }
  250.         if ($class instanceof DataObject\ClassDefinition) {
  251.             $this->setClass($class);
  252.         }
  253.     }
  254.     /**
  255.      * @return string
  256.      */
  257.     protected function getClassName(): string
  258.     {
  259.         return '\\Pimcore\\Model\\DataObject\\' ucfirst($this->getClass()->getName());
  260.     }
  261.     /**
  262.      * @param array $params
  263.      *
  264.      * @return bool
  265.      */
  266.     public function checkParams($params): bool
  267.     {
  268.         if (!array_key_exists('email'$params)) {
  269.             return false;
  270.         }
  271.         if (strlen($params['email']) < ||
  272.             !strpos($params['email'], '@') ||
  273.             !strpos($params['email'], '.')) {
  274.             return false;
  275.         }
  276.         return true;
  277.     }
  278.     /**
  279.      * @param array $params
  280.      *
  281.      * @return DataObject\Concrete
  282.      *
  283.      * @throws Exception
  284.      */
  285.     public function subscribe($params)
  286.     {
  287.         $onlyCreateVersion false;
  288.         $className $this->getClassName();
  289.         /** @var DataObject\Concrete $object */
  290.         $object = new $className;
  291.         // check for existing e-mail
  292.         $existingObject $className::getByEmail($params['email'], 1);
  293.         if ($existingObject) {
  294.             // if there's an existing user with this email address, do not overwrite the contents, but create a new
  295.             // version which will be published as soon as the contact gets verified (token/email)
  296.             $object $existingObject;
  297.             $onlyCreateVersion true;
  298.         }
  299.         if (!array_key_exists('email'$params)) {
  300.             throw new InvalidArgumentException("key 'email' is a mandatory parameter");
  301.         }
  302.         $object->setValues($params);
  303.         if (!$object->getParentId()) {
  304.             $object->setParentId(1);
  305.         }
  306.         $object->setNewsletterActive(true);
  307.         $object->setCreationDate(time());
  308.         $object->setModificationDate(time());
  309.         $object->setUserModification(0);
  310.         $object->setUserOwner(0);
  311.         $object->setPublished(true);
  312.         $object->setKey(File::getValidFilename(uniqid($object->getEmail(), true)));
  313.         if (!$onlyCreateVersion) {
  314.             $object->save();
  315.         }
  316.         // generate token
  317.         $token base64_encode(json_encode([
  318.             'salt' => md5(microtime()),
  319.             'email' => $object->getEmail(),
  320.             'id' => $object->getId(),
  321.         ]));
  322.         $token str_replace('=''~'$token); // base64 can contain = which isn't safe in URL's
  323.         $object->setProperty('token''text'$token);
  324.         if (!$onlyCreateVersion) {
  325.             $object->save();
  326.         } else {
  327.             $object->saveVersion();
  328.         }
  329.         $this->addNoteOnObject($object'subscribe');
  330.         return $object;
  331.     }
  332.     /**
  333.      * @param DataObject\Concrete $object
  334.      * @param Document $mailDocument
  335.      * @param array $params
  336.      *
  337.      * @throws Exception
  338.      */
  339.     public function sendConfirmationMail($object$mailDocument$params = []): void
  340.     {
  341.         $defaultParameters = [
  342.             'gender' => $object->getGender(),
  343.             'firstname' => $object->getFirstname(),
  344.             'lastname' => $object->getLastname(),
  345.             'email' => $object->getEmail(),
  346.             'token' => $object->getProperty('token'),
  347.             'object' => $object,
  348.         ];
  349.         $params array_merge($defaultParameters$params);
  350.         $mail = new Mail();
  351.         $mail->addTo($object->getEmail());
  352.         $mail->setDocument($mailDocument);
  353.         $mail->setParams($params);
  354.         $mail->send();
  355.     }
  356.     /**
  357.      * @param string $token
  358.      *
  359.      * @return DataObject\Concrete|null
  360.      */
  361.     public function getObjectByToken($token): ?DataObject\Concrete
  362.     {
  363.         $originalToken $token;
  364.         $token str_replace('~''='$token); // base64 can contain = which isn't safe in URL's
  365.         $data json_decode(base64_decode($token), true);
  366.         if ($data && $object DataObject\Concrete::getById($data['id'])) {
  367.             if ($version $object->getLatestVersion()) {
  368.                 $object $version->getData();
  369.             }
  370.             if ($object->getProperty('token') === $originalToken && $object->getEmail() === $data['email']) {
  371.                 return $object;
  372.             }
  373.         }
  374.         return null;
  375.     }
  376.     /**
  377.      * @param string $token
  378.      *
  379.      * @return bool
  380.      *
  381.      * @throws Exception
  382.      */
  383.     public function confirm($token): bool
  384.     {
  385.         $object $this->getObjectByToken($token);
  386.         if ($object) {
  387.             if ($version $object->getLatestVersion()) {
  388.                 $object $version->getData();
  389.                 $object->setPublished(true);
  390.             }
  391.             $object->setNewsletterConfirmed(true);
  392.             $object->save();
  393.             $this->addNoteOnObject($object'confirm');
  394.             return true;
  395.         }
  396.         return false;
  397.     }
  398.     /**
  399.      * @param string $token
  400.      *
  401.      * @return bool
  402.      *
  403.      * @throws Exception
  404.      */
  405.     public function unsubscribeByToken($token): bool
  406.     {
  407.         $object $this->getObjectByToken($token);
  408.         if ($object) {
  409.             return $this->unsubscribe($object);
  410.         }
  411.         return false;
  412.     }
  413.     /**
  414.      * @param string $email
  415.      *
  416.      * @return bool
  417.      *
  418.      * @throws Exception
  419.      */
  420.     public function unsubscribeByEmail($email): bool
  421.     {
  422.         $className $this->getClassName();
  423.         $objects $className::getByEmail($email);
  424.         if (count($objects)) {
  425.             foreach ($objects as $object) {
  426.                 $this->unsubscribe($object);
  427.             }
  428.             return true;
  429.         }
  430.         return false;
  431.     }
  432.     /**
  433.      * @param DataObject\AbstractObject $object
  434.      *
  435.      * @return bool
  436.      *
  437.      * @throws Exception
  438.      */
  439.     public function unsubscribe(DataObject\AbstractObject $object): bool
  440.     {
  441.         if ($object) {
  442.             $object->setNewsletterActive(false);
  443.             $object->save();
  444.             $this->addNoteOnObject($object'unsubscribe');
  445.             return true;
  446.         }
  447.         return false;
  448.     }
  449.     /**
  450.      * @param DataObject\Concrete $object
  451.      * @param string $title
  452.      */
  453.     public function addNoteOnObject($object$title): void
  454.     {
  455.         $note = new Model\Element\Note();
  456.         $note->setElement($object);
  457.         $note->setDate(time());
  458.         $note->setType('newsletter');
  459.         $note->setTitle($title);
  460.         $note->setUser(0);
  461.         $note->setData([
  462.             'ip' => [
  463.                 'type' => 'text',
  464.                 'data' => Tool::getClientIp(),
  465.             ],
  466.         ]);
  467.         $note->save();
  468.     }
  469.     /**
  470.      * Checks if e-mail address already
  471.      * exists in the database.
  472.      *
  473.      * @param array $params
  474.      *
  475.      * @return bool
  476.      */
  477.     public function isEmailExists($params): bool
  478.     {
  479.         $className $this->getClassName();
  480.         $existingObject $className::getByEmail($params['email'], 1);
  481.         if ($existingObject) {
  482.             return true;
  483.         }
  484.         return false;
  485.     }
  486.     /**
  487.      * @param DataObject\ClassDefinition $class
  488.      */
  489.     public function setClass($class): void
  490.     {
  491.         $this->class $class;
  492.     }
  493.     /**
  494.      * @return DataObject\ClassDefinition
  495.      */
  496.     public function getClass(): DataObject\ClassDefinition
  497.     {
  498.         return $this->class;
  499.     }
  500.     /**
  501.      * Checks if domain of email has a MX record
  502.      *
  503.      * @param string $email
  504.      *
  505.      * @return bool
  506.      */
  507.     public static function to_domain_exists($email): bool
  508.     {
  509.         [, $domain] = explode('@'$email);
  510.         return checkdnsrr($domain'MX');
  511.     }
  512. }