123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Email\Model;
- use Magento\Framework\App\Filesystem\DirectoryList;
- use Magento\Framework\App\TemplateTypesInterface;
- use Magento\Framework\DataObject;
- use Magento\Framework\Exception\LocalizedException;
- use Magento\Framework\Model\AbstractModel;
- use Magento\Store\Model\Information as StoreInformation;
- use Magento\Store\Model\ScopeInterface;
- use Magento\Store\Model\Store;
- /**
- * Template model class
- *
- * @author Magento Core Team <core@magentocommerce.com>
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- * @SuppressWarnings(PHPMD.TooManyFields)
- * @api
- * @since 100.0.2
- */
- abstract class AbstractTemplate extends AbstractModel implements TemplateTypesInterface
- {
- /**
- * Default design area for emulation
- */
- const DEFAULT_DESIGN_AREA = 'frontend';
- /**
- * Default path to email logo
- */
- const DEFAULT_LOGO_FILE_ID = 'Magento_Email::logo_email.png';
- /**
- * Email logo url
- */
- const XML_PATH_DESIGN_EMAIL_LOGO = 'design/email/logo';
- /**
- * Email logo alt text
- */
- const XML_PATH_DESIGN_EMAIL_LOGO_ALT = 'design/email/logo_alt';
- /**
- * Email logo width
- */
- const XML_PATH_DESIGN_EMAIL_LOGO_WIDTH = 'design/email/logo_width';
- /**
- * Email logo height
- */
- const XML_PATH_DESIGN_EMAIL_LOGO_HEIGHT = 'design/email/logo_height';
- /**
- * Configuration of design package for template
- *
- * @var DataObject
- */
- private $designConfig;
- /**
- * Whether template is child of another template
- *
- * @var bool
- */
- private $isChildTemplate = false;
- /**
- * Email template filter
- *
- * @var \Magento\Email\Model\Template\Filter
- */
- private $templateFilter;
- /**
- * Configuration of emulated design package.
- *
- * @var DataObject|boolean
- */
- private $emulatedDesignConfig = false;
- /**
- * Package area
- *
- * @var string
- */
- private $area;
- /**
- * Store id
- *
- * @var int
- */
- private $store;
- /**
- * Tracks whether design has been applied within the context of this template model.
- *
- * Important as there are multiple entry points for the applyDesignConfig method.
- *
- * @var bool
- */
- private $hasDesignBeenApplied = false;
- /**
- * @var \Magento\Email\Model\TemplateFactory
- */
- protected $templateFactory = null;
- /**
- * Design package instance
- *
- * @var \Magento\Framework\View\DesignInterface
- */
- protected $design = null;
- /**
- * @var \Magento\Store\Model\App\Emulation
- */
- protected $appEmulation;
- /**
- * @var \Magento\Store\Model\StoreManagerInterface
- */
- protected $storeManager;
- /**
- * Asset service
- *
- * @var \Magento\Framework\View\Asset\Repository
- */
- protected $assetRepo;
- /**
- * @var \Magento\Framework\Filesystem
- */
- protected $filesystem;
- /**
- * Scope config
- *
- * @var \Magento\Framework\App\Config\ScopeConfigInterface
- */
- protected $scopeConfig;
- /**
- * @var \Magento\Email\Model\Template\Config
- */
- protected $emailConfig;
- /**
- * @var \Magento\Framework\Filter\FilterManager
- */
- protected $filterManager;
- /**
- * @var \Magento\Framework\UrlInterface
- */
- private $urlModel;
- /**
- * @param \Magento\Framework\Model\Context $context
- * @param \Magento\Framework\View\DesignInterface $design
- * @param \Magento\Framework\Registry $registry
- * @param \Magento\Store\Model\App\Emulation $appEmulation
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Framework\View\Asset\Repository $assetRepo
- * @param \Magento\Framework\Filesystem $filesystem
- * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
- * @param \Magento\Email\Model\Template\Config $emailConfig
- * @param \Magento\Email\Model\TemplateFactory $templateFactory
- * @param \Magento\Framework\Filter\FilterManager $filterManager
- * @param \Magento\Framework\UrlInterface $urlModel
- * @param array $data
- *
- * @SuppressWarnings(PHPMD.ExcessiveParameterList)
- */
- public function __construct(
- \Magento\Framework\Model\Context $context,
- \Magento\Framework\View\DesignInterface $design,
- \Magento\Framework\Registry $registry,
- \Magento\Store\Model\App\Emulation $appEmulation,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Framework\View\Asset\Repository $assetRepo,
- \Magento\Framework\Filesystem $filesystem,
- \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
- \Magento\Email\Model\Template\Config $emailConfig,
- \Magento\Email\Model\TemplateFactory $templateFactory,
- \Magento\Framework\Filter\FilterManager $filterManager,
- \Magento\Framework\UrlInterface $urlModel,
- array $data = []
- ) {
- $this->design = $design;
- $this->area = isset($data['area']) ? $data['area'] : null;
- $this->store = isset($data['store']) ? $data['store'] : null;
- $this->appEmulation = $appEmulation;
- $this->storeManager = $storeManager;
- $this->assetRepo = $assetRepo;
- $this->filesystem = $filesystem;
- $this->scopeConfig = $scopeConfig;
- $this->emailConfig = $emailConfig;
- $this->templateFactory = $templateFactory;
- $this->filterManager = $filterManager;
- $this->urlModel = $urlModel;
- parent::__construct($context, $registry, null, null, $data);
- }
- /**
- * Get contents of the included template for template directive
- *
- * @param string $configPath
- * @param array $variables
- * @return string
- */
- public function getTemplateContent($configPath, array $variables)
- {
- $template = $this->getTemplateInstance();
- // Ensure child templates have the same area/store context as parent
- $template->setDesignConfig($this->getDesignConfig()->toArray())
- ->loadByConfigPath($configPath, $variables)
- ->setTemplateType($this->getType())
- ->setIsChildTemplate(true);
- // automatically strip tags if in a plain-text parent
- if ($this->isPlain()) {
- $templateText = $this->filterManager->stripTags($template->getTemplateText());
- $template->setTemplateText(trim($templateText));
- }
- $processedTemplate = $template->getProcessedTemplate($variables);
- if ($this->isPlain()) {
- $processedTemplate = trim($processedTemplate);
- }
- return $processedTemplate;
- }
- /**
- * Return a new instance of the template object. Used by the template directive.
- *
- * @return \Magento\Email\Model\AbstractTemplate
- */
- protected function getTemplateInstance()
- {
- return $this->templateFactory->create();
- }
- /**
- * Load template from database when overridden in configuration or load default from relevant file system location.
- *
- * @param string $configPath
- * @return \Magento\Email\Model\AbstractTemplate
- */
- public function loadByConfigPath($configPath)
- {
- $store = $this->getDesignConfig()->getStore();
- $templateId = $this->scopeConfig->getValue($configPath, ScopeInterface::SCOPE_STORE, $store);
- if (is_numeric($templateId)) {
- $this->load($templateId);
- } else {
- $this->loadDefault($templateId);
- }
- return $this;
- }
- /**
- * Load default email template
- *
- * @param string $templateId
- * @return $this
- */
- public function loadDefault($templateId)
- {
- $designParams = $this->getDesignParams();
- $templateFile = $this->emailConfig->getTemplateFilename($templateId, $designParams);
- $templateType = $this->emailConfig->getTemplateType($templateId);
- $templateTypeCode = $templateType == 'html' ? self::TYPE_HTML : self::TYPE_TEXT;
- $this->setTemplateType($templateTypeCode);
- $rootDirectory = $this->filesystem->getDirectoryRead(DirectoryList::ROOT);
- $templateText = $rootDirectory->readFile($rootDirectory->getRelativePath($templateFile));
- /**
- * trim copyright message
- */
- if (preg_match('/^<!--[\w\W]+?-->/m', $templateText, $matches) && strpos($matches[0], 'Copyright') !== false) {
- $templateText = str_replace($matches[0], '', $templateText);
- }
- if (preg_match('/<!--@subject\s*(.*?)\s*@-->/u', $templateText, $matches)) {
- $this->setTemplateSubject($matches[1]);
- $templateText = str_replace($matches[0], '', $templateText);
- }
- if (preg_match('/<!--@vars\s*((?:.)*?)\s*@-->/us', $templateText, $matches)) {
- $this->setData('orig_template_variables', str_replace("\n", '', $matches[1]));
- $templateText = str_replace($matches[0], '', $templateText);
- }
- if (preg_match('/<!--@styles\s*(.*?)\s*@-->/s', $templateText, $matches)) {
- $this->setTemplateStyles($matches[1]);
- $templateText = str_replace($matches[0], '', $templateText);
- }
- // Remove comment lines and extra spaces
- $templateText = trim(preg_replace('#\{\*.*\*\}#suU', '', $templateText));
- $this->setTemplateText($templateText);
- $this->setId($templateId);
- return $this;
- }
- /**
- * Process email template code
- *
- * @param array $variables
- * @return string
- * @throws \Magento\Framework\Exception\MailException
- */
- public function getProcessedTemplate(array $variables = [])
- {
- $processor = $this->getTemplateFilter()
- ->setUseSessionInUrl(false)
- ->setPlainTemplateMode($this->isPlain())
- ->setIsChildTemplate($this->isChildTemplate())
- ->setTemplateProcessor([$this, 'getTemplateContent']);
- $variables['this'] = $this;
- $isDesignApplied = $this->applyDesignConfig();
- // Set design params so that CSS will be loaded from the proper theme
- $processor->setDesignParams($this->getDesignParams());
- if (isset($variables['subscriber'])) {
- $storeId = $variables['subscriber']->getStoreId();
- } else {
- $storeId = $this->getDesignConfig()->getStore();
- }
- $processor->setStoreId($storeId);
- // Populate the variables array with store, store info, logo, etc. variables
- $variables = $this->addEmailVariables($variables, $storeId);
- $processor->setVariables($variables);
- try {
- $result = $processor->filter($this->getTemplateText());
- } catch (\Exception $e) {
- $this->cancelDesignConfig();
- throw new \LogicException(__($e->getMessage()), $e->getCode(), $e);
- }
- if ($isDesignApplied) {
- $this->cancelDesignConfig();
- }
- return $result;
- }
- /**
- * Get default email logo image
- *
- * @return string
- */
- public function getDefaultEmailLogo()
- {
- $designParams = $this->getDesignParams();
- return $this->assetRepo->getUrlWithParams(
- self::DEFAULT_LOGO_FILE_ID,
- $designParams
- );
- }
- /**
- * Return logo URL for emails. Take logo from theme if custom logo is undefined
- *
- * @param Store|int|string $store
- * @return string
- */
- protected function getLogoUrl($store)
- {
- $store = $this->storeManager->getStore($store);
- $fileName = $this->scopeConfig->getValue(
- self::XML_PATH_DESIGN_EMAIL_LOGO,
- ScopeInterface::SCOPE_STORE,
- $store
- );
- if ($fileName) {
- $uploadDir = \Magento\Email\Model\Design\Backend\Logo::UPLOAD_DIR;
- $mediaDirectory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA);
- if ($mediaDirectory->isFile($uploadDir . '/' . $fileName)) {
- return $this->storeManager->getStore()->getBaseUrl(
- \Magento\Framework\UrlInterface::URL_TYPE_MEDIA
- ) . $uploadDir . '/' . $fileName;
- }
- }
- return $this->getDefaultEmailLogo();
- }
- /**
- * Return logo alt for emails
- *
- * @param Store|int|string $store
- * @return string
- */
- protected function getLogoAlt($store)
- {
- $store = $this->storeManager->getStore($store);
- $alt = $this->scopeConfig->getValue(
- self::XML_PATH_DESIGN_EMAIL_LOGO_ALT,
- ScopeInterface::SCOPE_STORE,
- $store
- );
- if ($alt) {
- return $alt;
- }
- return $store->getFrontendName();
- }
- /**
- * Add variables that are used by transactional and newsletter emails
- *
- * @param array $variables
- * @param null|string|bool|int|Store $storeId
- * @return mixed
- *
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- protected function addEmailVariables($variables, $storeId)
- {
- $store = $this->storeManager->getStore($storeId);
- if (!isset($variables['store'])) {
- $variables['store'] = $store;
- }
- if (!isset($variables['logo_url'])) {
- $variables['logo_url'] = $this->getLogoUrl($storeId);
- }
- if (!isset($variables['logo_alt'])) {
- $variables['logo_alt'] = $this->getLogoAlt($storeId);
- }
- if (!isset($variables['logo_width'])) {
- $variables['logo_width'] = $this->scopeConfig->getValue(
- self::XML_PATH_DESIGN_EMAIL_LOGO_WIDTH,
- ScopeInterface::SCOPE_STORE,
- $store
- );
- }
- if (!isset($variables['logo_height'])) {
- $variables['logo_height'] = $this->scopeConfig->getValue(
- self::XML_PATH_DESIGN_EMAIL_LOGO_HEIGHT,
- ScopeInterface::SCOPE_STORE,
- $store
- );
- }
- if (!isset($variables['store_phone'])) {
- $variables['store_phone'] = $this->scopeConfig->getValue(
- StoreInformation::XML_PATH_STORE_INFO_PHONE,
- ScopeInterface::SCOPE_STORE,
- $store
- );
- }
- if (!isset($variables['store_hours'])) {
- $variables['store_hours'] = $this->scopeConfig->getValue(
- StoreInformation::XML_PATH_STORE_INFO_HOURS,
- ScopeInterface::SCOPE_STORE,
- $store
- );
- }
- if (!isset($variables['store_email'])) {
- $variables['store_email'] = $this->scopeConfig->getValue(
- 'trans_email/ident_support/email',
- ScopeInterface::SCOPE_STORE,
- $store
- );
- }
- // If template is text mode, don't include styles
- if (!$this->isPlain() && !isset($variables['template_styles'])) {
- $variables['template_styles'] = $this->getTemplateStyles();
- }
- return $variables;
- }
- /**
- * Apply design config so that emails are processed within the context of the appropriate area/store/theme.
- * Can be called multiple times without issue.
- *
- * @return bool
- */
- protected function applyDesignConfig()
- {
- // Only run app emulation if this is the parent template and emulation isn't already running.
- // Otherwise child will run inside parent emulation.
- if ($this->isChildTemplate() || $this->hasDesignBeenApplied) {
- return false;
- }
- $this->hasDesignBeenApplied = true;
- $designConfig = $this->getDesignConfig();
- $storeId = $designConfig->getStore();
- $area = $designConfig->getArea();
- if ($storeId !== null) {
- // Force emulation in case email is being sent from same store so that theme will be loaded. Helpful
- // for situations where emails may be sent from bootstrap files that load frontend store, but not theme
- $this->appEmulation->startEnvironmentEmulation($storeId, $area, true);
- }
- return true;
- }
- /**
- * Revert design settings to previous
- *
- * @return $this
- */
- protected function cancelDesignConfig()
- {
- $this->appEmulation->stopEnvironmentEmulation();
- $this->hasDesignBeenApplied = false;
- return $this;
- }
- /**
- * Store the area associated with a template so that it will be returned by getDesignConfig and getDesignParams
- *
- * @param string $templateId
- * @return $this
- */
- public function setForcedArea($templateId)
- {
- if ($this->area === null) {
- $this->area = $this->emailConfig->getTemplateArea($templateId);
- }
- return $this;
- }
- /**
- * Manually set a theme that will be used by getParams
- *
- * Used to force the loading of an email template from a specific theme
- *
- * @param string $templateId
- * @param string $theme
- * @return $this
- */
- public function setForcedTheme($templateId, $theme)
- {
- $area = $this->emailConfig->getTemplateArea($templateId);
- $this->design->setDesignTheme($theme, $area);
- return $this;
- }
- /**
- * Returns the design params for the template being processed
- *
- * @return array
- */
- public function getDesignParams()
- {
- return [
- // Retrieve area from getDesignConfig, rather than the getDesignTheme->getArea(), as the latter doesn't
- // return the emulated area
- 'area' => $this->getDesignConfig()->getArea(),
- 'theme' => $this->design->getDesignTheme()->getCode(),
- 'themeModel' => $this->design->getDesignTheme(),
- 'locale' => $this->design->getLocale(),
- ];
- }
- /**
- * Get design configuration data
- *
- * @return DataObject
- */
- public function getDesignConfig()
- {
- if ($this->designConfig === null) {
- if ($this->area === null) {
- $this->area = $this->design->getArea();
- }
- if ($this->store === null) {
- $this->store = $this->storeManager->getStore()->getId();
- }
- $this->designConfig = new DataObject(
- ['area' => $this->area, 'store' => $this->store]
- );
- }
- return $this->designConfig;
- }
- /**
- * Initialize design information for template processing
- *
- * @param array $config
- * @return $this
- * @throws LocalizedException
- */
- public function setDesignConfig(array $config)
- {
- if (!isset($config['area']) || !isset($config['store'])) {
- throw new LocalizedException(
- __('The design config needs an area and a store. Verify that both are set and try again.')
- );
- }
- $this->getDesignConfig()->setData($config);
- return $this;
- }
- /**
- * Check whether template is child of another template
- *
- * @return bool
- */
- public function isChildTemplate()
- {
- return $this->isChildTemplate;
- }
- /**
- * Set whether template is child of another template
- *
- * @param bool $isChildTemplate
- * @return $this
- */
- public function setIsChildTemplate($isChildTemplate)
- {
- $this->isChildTemplate = (bool) $isChildTemplate;
- return $this;
- }
- /**
- * Declare template processing filter
- *
- * @param \Magento\Email\Model\Template\Filter $filter
- * @return $this
- */
- public function setTemplateFilter(Template\Filter $filter)
- {
- $this->templateFilter = $filter;
- return $this;
- }
- /**
- * Get filter object for template processing
- *
- * @return \Magento\Email\Model\Template\Filter
- */
- public function getTemplateFilter()
- {
- if (empty($this->templateFilter)) {
- $this->templateFilter = $this->getFilterFactory()->create();
- $this->templateFilter->setUseAbsoluteLinks($this->getUseAbsoluteLinks())
- ->setStoreId($this->getDesignConfig()->getStore())
- ->setUrlModel($this->urlModel);
- }
- return $this->templateFilter;
- }
- /**
- * Save current design config and replace with design config from specified store
- * Event is not dispatched.
- *
- * @param null|bool|int|string $storeId
- * @param string $area
- * @return void
- */
- public function emulateDesign($storeId, $area = self::DEFAULT_DESIGN_AREA)
- {
- if ($storeId !== null && $storeId !== false) {
- // save current design settings
- $this->emulatedDesignConfig = clone $this->getDesignConfig();
- if ($this->getDesignConfig()->getStore() != $storeId
- || $this->getDesignConfig()->getArea() != $area
- ) {
- $this->setDesignConfig(['area' => $area, 'store' => $storeId]);
- $this->applyDesignConfig();
- }
- } else {
- $this->emulatedDesignConfig = false;
- }
- }
- /**
- * Revert to last design config, used before emulation
- *
- * @return void
- */
- public function revertDesign()
- {
- if ($this->emulatedDesignConfig) {
- $this->setDesignConfig($this->emulatedDesignConfig->getData());
- $this->cancelDesignConfig();
- $this->emulatedDesignConfig = false;
- }
- }
- /**
- * Return true if template type eq text
- *
- * @return boolean
- */
- public function isPlain()
- {
- return $this->getType() == self::TYPE_TEXT;
- }
- /**
- * Getter for filter factory that is specific to the type of template being processed
- *
- * @return mixed
- */
- abstract protected function getFilterFactory();
- /**
- * Getter for template type
- *
- * @return int|string
- */
- abstract public function getType();
- /**
- * Generate URL for the specified store.
- *
- * @param Store $store
- * @param string $route
- * @param array $params
- * @return string
- */
- public function getUrl(Store $store, $route = '', $params = [])
- {
- $url = $this->urlModel->setScope($store);
- if ($this->storeManager->getStore()->getId() != $store->getId()) {
- $params['_scope_to_url'] = true;
- }
- return $url->getUrl($route, $params);
- }
- }
|