├── .gitignore ├── Block └── Adminhtml │ ├── Edit │ └── Tab │ │ └── Mail │ │ └── Grid │ │ └── Renderer │ │ ├── Action.php │ │ ├── Customer.php │ │ ├── Recipients.php │ │ ├── Sent.php │ │ └── Template.php │ ├── Grid.php │ ├── Mail │ ├── Edit │ │ ├── Form.php │ │ ├── Info.php │ │ └── View.php │ └── Send │ │ └── Form.php │ ├── MailOverview.php │ ├── MailTab.php │ └── Widget │ └── Grid │ └── Column │ └── Filter │ └── Text.php ├── CHANGELOG.md ├── Controller └── Adminhtml │ ├── Index │ └── Mail.php │ ├── Mail.php │ └── Mail │ ├── Attachment.php │ ├── Edit.php │ ├── Overview.php │ ├── Send.php │ └── SendPost.php ├── LICENSE.txt ├── Model ├── Config.php ├── Config │ └── Source │ │ ├── Storage.php │ │ └── Type.php ├── Mail.php ├── Mail │ ├── Attachment.php │ ├── AttachmentCollection.php │ ├── AttachmentCollectionInterface.php │ ├── AttachmentInterface.php │ ├── Message.php │ └── MessageInterface.php ├── ResourceModel │ ├── Mail.php │ └── Mail │ │ └── Collection.php ├── Simulation │ └── Config.php ├── Storages │ ├── Base.php │ ├── DefaultStorage.php │ └── StorageInterface.php ├── Template │ └── TransportBuilder.php └── Transports │ ├── Base.php │ └── TransportInterface.php ├── README.md ├── Setup ├── InstallSchema.php └── UpgradeSchema.php ├── composer.json ├── docs ├── enhanced-magento2-customer-detail-menu.png ├── local-storeage-for-mails-as-json.png ├── magento2-config-mail-enhancement.png ├── magento2-customer-mail-transactional-review-detail.png ├── magento2-customer-mail-transactional-review-with-mail.png ├── magento2-customer-mail-transactional-review.png ├── magento2-customer-menu.png ├── magento2-guest-email-review-and-resending.jpg ├── magento2-mail-resending-and-recalculation.png └── magento2-mail-stored-as-json.png ├── etc ├── acl.xml ├── adminhtml │ ├── menu.xml │ ├── routes.xml │ └── system.xml ├── cache.xml ├── config.xml ├── di.xml └── module.xml ├── i18n └── en_US.csv ├── registration.php └── view └── adminhtml ├── layout ├── customer_index_edit.xml ├── customer_index_mail.xml ├── customer_mail_edit.xml ├── customer_mail_overview.xml ├── customer_mail_send.xml └── overview_block.xml └── templates └── customer ├── edit └── tab │ └── mail.phtml └── mail ├── edit ├── info.phtml └── view.phtml └── send └── form.phtml /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | /.idea 3 | *~ 4 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/Tab/Mail/Grid/Renderer/Action.php: -------------------------------------------------------------------------------- 1 | getUrl('customer/mail/edit', array('id' => $row->getId())); 22 | return "View"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/Tab/Mail/Grid/Renderer/Customer.php: -------------------------------------------------------------------------------- 1 | getData($this->getColumn()->getIndex()); 22 | if($customerId > 0) { 23 | return '' . $customerId . ''; 24 | } 25 | 26 | return nl2br(htmlspecialchars($row->getData($this->getColumn()->getIndex()))); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/Tab/Mail/Grid/Renderer/Recipients.php: -------------------------------------------------------------------------------- 1 | getData($this->getColumn()->getIndex()), true); 22 | if(!empty($recipients)) 23 | { 24 | $recipientsString = htmlspecialchars(implode(', ', $recipients)); 25 | if(strlen($recipientsString) < 15) 26 | { 27 | return $recipientsString; 28 | } 29 | 30 | return substr($recipientsString, 0, 12) . '...'; 31 | } 32 | 33 | return ''; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/Tab/Mail/Grid/Renderer/Sent.php: -------------------------------------------------------------------------------- 1 | getData($this->getColumn()->getIndex()) > 0) ? __('yes') : __('no'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/Tab/Mail/Grid/Renderer/Template.php: -------------------------------------------------------------------------------- 1 | getData($this->getColumn()->getIndex()))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Block/Adminhtml/Grid.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | namespace Shockwavemk\Mail\Base\Block\Adminhtml; 21 | 22 | class Grid extends \Magento\Backend\Block\Widget\Grid\Extended 23 | { 24 | /** 25 | * Review action pager 26 | * 27 | * @var \Magento\Review\Helper\Action\Pager 28 | */ 29 | protected $_reviewActionPager = null; 30 | 31 | /** 32 | * Review data 33 | * 34 | * @var \Magento\Review\Helper\Data 35 | */ 36 | protected $_reviewData = null; 37 | 38 | /** 39 | * Core registry 40 | * 41 | * @var \Magento\Framework\Registry 42 | */ 43 | protected $_coreRegistry = null; 44 | 45 | /** 46 | * Review collection model factory 47 | * 48 | * @var \Magento\Review\Model\ResourceModel\Review\Product\CollectionFactory 49 | */ 50 | protected $_productsFactory; 51 | 52 | /** 53 | * Review model factory 54 | * 55 | * @var \Magento\Review\Model\ReviewFactory 56 | */ 57 | protected $_reviewFactory; 58 | /** 59 | * @var \Shockwavemk\Mail\Base\Model\ResourceModel\Mail\Collection 60 | */ 61 | private $collection; 62 | 63 | /** 64 | * @param \Magento\Backend\Block\Template\Context $context 65 | * @param \Magento\Backend\Helper\Data $backendHelper 66 | * @param \Magento\Framework\Registry $coreRegistry 67 | * @param \Shockwavemk\Mail\Base\Model\ResourceModel\Mail\Collection $collection 68 | * @param array $data 69 | */ 70 | public function __construct( 71 | \Magento\Backend\Block\Template\Context $context, 72 | \Magento\Backend\Helper\Data $backendHelper, 73 | \Magento\Framework\Registry $coreRegistry, 74 | \Shockwavemk\Mail\Base\Model\ResourceModel\Mail\Collection $collection, 75 | array $data = [] 76 | ) { 77 | $this->_coreRegistry = $coreRegistry; 78 | $this->_collection = $collection; 79 | parent::__construct($context, $backendHelper, $data); 80 | } 81 | 82 | /** 83 | * Initialize grid 84 | * 85 | * @return void 86 | */ 87 | protected function _construct() 88 | { 89 | parent::_construct(); 90 | $this->setId('mailGrid'); 91 | $this->setDefaultSort('sent_at'); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Block/Adminhtml/Mail/Edit/Form.php: -------------------------------------------------------------------------------- 1 | coreRegistry = $registry; 140 | $this->accountManagement = $accountManagement; 141 | $this->groupRepository = $groupRepository; 142 | $this->customerDataFactory = $customerDataFactory; 143 | $this->addressHelper = $addressHelper; 144 | $this->dateTime = $dateTime; 145 | $this->addressMapper = $addressMapper; 146 | $this->dataObjectHelper = $dataObjectHelper; 147 | $this->customerLogger = $customerLogger; 148 | $this->manager = $manager; 149 | 150 | $mailId = $this->_request->getParam('id'); 151 | $this->_mail = $manager->get('\Shockwavemk\Mail\Base\Model\Mail'); 152 | $this->_mail->load($mailId); 153 | } 154 | 155 | public function getMail() 156 | { 157 | return $this->_mail; 158 | } 159 | 160 | public function getStoreName() 161 | { 162 | return $this->_storeManager->getStore($this->_mail->getStoreId())->getName(); 163 | } 164 | 165 | public function htmlEscape($text) 166 | { 167 | return $text; 168 | } 169 | 170 | public function getDownloadUrl(\Shockwavemk\Mail\Base\Model\Mail\Attachment $attachment) 171 | { 172 | return $this->getUrl('customer/mail/attachment', 173 | array( 174 | 'id' => base64_encode($this->getMail()->getId()), 175 | 'path' => base64_encode($attachment->getFilePath()) 176 | ) 177 | ); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Block/Adminhtml/Mail/Edit/View.php: -------------------------------------------------------------------------------- 1 | _blockGroup = 'Shockwavemk_Mail_Base'; 36 | $this->_controller = 'adminhtml_mail'; 37 | $this->_mode = 'edit'; 38 | $this->_request = $context->getRequest(); 39 | $mailId = $this->_request->getParam('id'); 40 | $this->_mail = $manager->get('\Shockwavemk\Mail\Base\Model\Mail'); 41 | $this->_mail->load($mailId); 42 | 43 | parent::__construct($context, $data); 44 | } 45 | 46 | /** 47 | * Prepare the layout. 48 | * 49 | * @return $this 50 | */ 51 | protected function _prepareLayout() 52 | { 53 | $this->buttonList->remove('save'); 54 | $this->buttonList->remove('delete'); 55 | $this->buttonList->remove('reset'); 56 | 57 | $this->buttonList->add( 58 | 'send', 59 | [ 60 | 'label' => __('Send mail ...'), 61 | 'onclick' => "setLocation('{$this->getUrl('*/*/send', 62 | ['id' => $this->_mail->getId()] 63 | )}')", 64 | 'class' => 'task' 65 | ] 66 | ); 67 | 68 | $this->buttonList->add( 69 | 'send_post', 70 | [ 71 | 'label' => __('Resend mail'), 72 | 'onclick' => "setLocation('{$this->getUrl('*/*/sendPost', 73 | [ 74 | 'id' => $this->_mail->getId() 75 | ])}')", 76 | 'class' => 'task' 77 | ] 78 | ); 79 | 80 | return parent::_prepareLayout(); 81 | } 82 | 83 | /** 84 | * Get URL for back (reset) button 85 | * 86 | * @return string 87 | */ 88 | public function getBackUrl() 89 | { 90 | if(!empty($this->_mail->getCustomerId())) { 91 | return $this->getUrl('customer/index/edit', array('id' => $this->_mail->getCustomerId())); 92 | } 93 | 94 | return $this->getUrl('index/index'); 95 | } 96 | 97 | public function getMail() 98 | { 99 | return $this->_mail; 100 | } 101 | 102 | public function getUnstructuredData() 103 | { 104 | return 105 | "

Variables

{$this->getVarsOutput()}" . 106 | "

Recipient variables

{$this->getRecipientVariablesOutput()}" . 107 | "

Options

{$this->getOptionsOutput()}"; 108 | } 109 | 110 | /** 111 | * TODO 112 | * 113 | * @param $key 114 | * @param $value 115 | * @return array 116 | */ 117 | public function getCustomerRepresentation($key, $value) 118 | { 119 | /** @var \Magento\Customer\Model\Customer $value */ 120 | $url = $this->getUrl('customer/index/edit', array('id' => $value->getId())); 121 | return "{$key} (customer): {$value->getFirstname()} {$value->getLastname()}"; 122 | } 123 | 124 | /** 125 | * TODO 126 | * 127 | * @param $key 128 | * @param $value 129 | * @return array 130 | */ 131 | public function getOrderRepresentation($key, $value) 132 | { 133 | /** @var \Magento\Customer\Model\Customer $value */ 134 | $url = $this->getUrl('sales/order/view', array('order_id' => $value->getId())); 135 | return "{$key} (order): {$value->getIncrementId()}"; 136 | } 137 | 138 | /** 139 | * @param $key 140 | * @param $value 141 | * @return string 142 | */ 143 | public function getLinkRepresentation($key, $value) 144 | { 145 | return "{$key} (link): {$value}"; 146 | } 147 | 148 | /** 149 | * @param $key 150 | * @param $value 151 | * @return string 152 | */ 153 | public function getStoreRepresentation($key, $value) 154 | { 155 | /** @var \Magento\Store\Model\Store $value */ 156 | return "{$key} (store): {$value->getName()}"; 157 | } 158 | 159 | /** 160 | * TODO 161 | * 162 | * @param $key 163 | * @param $value 164 | * @return array 165 | */ 166 | public function getShipmentRepresentation($key, $value) 167 | { 168 | /** @var \Magento\Customer\Model\Customer $value */ 169 | $url = $this->getUrl('sales/shipment/view', array('shipment_id' => $value->getId())); 170 | return "{$key} (shipment): {$value->getIncrementId()}"; 171 | } 172 | 173 | /** 174 | * TODO 175 | * 176 | * @param $key 177 | * @param $value 178 | * @return array 179 | */ 180 | public function getInvoiceRepresentation($key, $value) 181 | { 182 | /** @var \Magento\Customer\Model\Customer $value */ 183 | $url = $this->getUrl('sales/invoice/view', array('invoice_id' => $value->getId())); 184 | return "{$key} (invoice): {$value->getIncrementId()}"; 185 | } 186 | 187 | /** 188 | * @param $key 189 | * @param $variable 190 | * @return string 191 | */ 192 | public function getVarOutput($key, $variable) 193 | { 194 | try 195 | { 196 | /** @var \Magento\Framework\Model\AbstractModel $variable */ 197 | if (is_subclass_of($variable, '\Magento\Framework\Model\AbstractModel') && $variable->getEntityType() == 'customer') { 198 | return $this->getCustomerRepresentation($key, $variable); 199 | 200 | } elseif (is_subclass_of($variable, '\Magento\Framework\Model\AbstractModel') && $variable->getEntityType() == 'order') { 201 | return $this->getOrderRepresentation($key, $variable); 202 | 203 | } elseif (is_subclass_of($variable, '\Magento\Framework\Model\AbstractModel') && $variable->getEntityType() == 'shipment') { 204 | return $this->getShipmentRepresentation($key, $variable); 205 | 206 | } elseif (is_subclass_of($variable, '\Magento\Framework\Model\AbstractModel') && $variable->getEntityType() == 'invoice') { 207 | return $this->getInvoiceRepresentation($key, $variable); 208 | 209 | } elseif (is_subclass_of($variable, 'Magento\Framework\Model\AbstractModel')) { 210 | return $key . ' : ' . json_encode($variable->getData(), JSON_PRETTY_PRINT); 211 | 212 | } elseif (is_subclass_of($variable, '\Magento\Eav\Model\Entity\Collection\AbstractCollection')) { 213 | /** @var \Magento\Eav\Model\Entity\Collection\AbstractCollection $variable*/ 214 | return $key . ' : ' . json_encode($variable->getData(), JSON_PRETTY_PRINT); 215 | 216 | } elseif (!filter_var($variable, FILTER_VALIDATE_URL) === false) { 217 | return $this->getLinkRepresentation($key, $variable); 218 | 219 | } elseif (is_string($variable)) { 220 | return $key . ' : ' . $variable; 221 | } 222 | else 223 | { 224 | return $key . ' : ' . json_encode($variable, JSON_PRETTY_PRINT); 225 | } 226 | } 227 | catch (\Exception $e) 228 | { 229 | $variable = 'variable can not be displayed'; 230 | } 231 | 232 | return $key . ' : ' . $variable; 233 | } 234 | 235 | /** 236 | * @return string 237 | */ 238 | public function getVarsOutput() 239 | { 240 | $output = []; 241 | $vars = $this->_mail->getVars(); 242 | if (!empty($vars)) { 243 | foreach ($vars as $key => $value) { 244 | $output[] = $this->getVarOutput($key, $value); 245 | } 246 | } 247 | return implode(',
', $output); 248 | } 249 | 250 | /** 251 | * @return string 252 | */ 253 | public function getRecipientVariablesOutput() 254 | { 255 | $output = []; 256 | $recipientVariables = $this->_mail->getRecipientVariables(); 257 | if (!empty($recipientVariables)) { 258 | $output[] = $recipientVariables; 259 | } 260 | return implode(',
', $output); 261 | } 262 | 263 | /** 264 | * @return string 265 | */ 266 | public function getOptionsOutput() 267 | { 268 | $output = []; 269 | $options = $this->_mail->getOptions(); 270 | if (!empty($options)) { 271 | foreach ($options as $key => $value) { 272 | $output[] = $this->getVarOutput($key, $value); 273 | } 274 | } 275 | return implode(',
', $output); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /Block/Adminhtml/Mail/Send/Form.php: -------------------------------------------------------------------------------- 1 | _request = $context->getRequest(); 30 | $mailId = $this->_request->getParam('id'); 31 | $this->_mail = $manager->get('\Shockwavemk\Mail\Base\Model\Mail'); 32 | $this->_mail->load($mailId); 33 | 34 | parent::__construct($context, $data); 35 | } 36 | 37 | public function getMail() 38 | { 39 | return $this->_mail; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Block/Adminhtml/MailOverview.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class MailOverview extends \Magento\Backend\Block\Widget\Grid\Container 14 | { 15 | /** 16 | * @return void 17 | */ 18 | protected function _construct() 19 | { 20 | $this->_controller = 'adminhtml'; 21 | $this->_blockGroup = 'Shockwavemk_Mail_Base'; 22 | $this->_headerText = __('Review Transactional Mails'); 23 | 24 | parent::_construct(); 25 | } 26 | 27 | /** 28 | * Create "New" button 29 | * 30 | * @return void 31 | */ 32 | protected function _addNewButton() 33 | { 34 | // do nothing 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Block/Adminhtml/MailTab.php: -------------------------------------------------------------------------------- 1 | coreRegistry = $registry; 41 | parent::__construct($context, $data); 42 | } 43 | 44 | /** 45 | * @inheritdoc 46 | */ 47 | public function canShowTab() 48 | { 49 | return $this->coreRegistry->registry(RegistryConstants::CURRENT_CUSTOMER_ID); 50 | } 51 | 52 | /** 53 | * Return Tab label 54 | * 55 | * @return \Magento\Framework\Phrase 56 | */ 57 | public function getTabLabel() 58 | { 59 | return __('Mail'); 60 | } 61 | 62 | /** 63 | * Return URL link to Tab content 64 | * 65 | * @return string 66 | */ 67 | public function getTabUrl() 68 | { 69 | return $this->getUrl('customer/*/mail', ['_current' => true]); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Block/Adminhtml/Widget/Grid/Column/Filter/Text.php: -------------------------------------------------------------------------------- 1 | $this->getValue()]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [unreleased] 6 | 7 | ## [v1.1.0] - 2016-10-10 8 | 9 | ### Added 10 | - Guest/customer mail review and resending grid 11 | 12 | ### Fixed 13 | - Fix resending: In case only one recipient is available, no implode 14 | several email addresses must take place. 15 | 16 | ## [v1.0.6] - 2016-09-05 17 | ### Fixed 18 | - On loop mail model is now reset on transport creation 19 | 20 | ## [v1.0.5] - 2016-09-05 21 | ### Fixed 22 | - SMTP attachment encoding default is now set to base64 if not given 23 | 24 | ## [v1.0.3] - 2016-08-30 25 | ### Fixed 26 | - Handover stroreid to get from name and mail for specific store 27 | 28 | ## [v1.0.2] - 2016-08-30 29 | ### Fixed 30 | - Bugfix for RMA resending 31 | 32 | ## [v1.0.0] - 2016-08-25 33 | 34 | ### Added 35 | - Apache2 license 36 | - First working adminhtml customer mail tracking 37 | 38 | ## Fixed 39 | - Multi-language cron scheduled mails 40 | 41 | ### Changed 42 | - Rename all "storeage" to "storage" 43 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Index/Mail.php: -------------------------------------------------------------------------------- 1 | initCurrentCustomer(); 18 | 19 | return $this->resultLayoutFactory->create(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Mail.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | namespace Shockwavemk\Mail\Base\Controller\Adminhtml; 12 | 13 | abstract class Mail extends \Magento\Backend\App\Action 14 | { 15 | /** 16 | * Check is allowed access 17 | * 18 | * @return bool 19 | */ 20 | protected function _isAllowed() 21 | { 22 | return true; // $this->_authorization->isAllowed('Magento_Newsletter::template'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Mail/Attachment.php: -------------------------------------------------------------------------------- 1 | _request->getParam('id'); 21 | 22 | /** @var \Shockwavemk\Mail\Base\Model\Mail $_mail */ 23 | $mail = $this->_objectManager->get('\Shockwavemk\Mail\Base\Model\Mail'); 24 | $mail->load(base64_decode($encodedId)); 25 | 26 | $encodedPath = $this->_request->getParam('path'); 27 | $localPath = base64_decode($encodedPath); 28 | 29 | /** @var \Shockwavemk\Mail\Base\Model\Mail\Attachment[] $attachments */ 30 | $attachments = $mail->getAttachments(); 31 | 32 | /** @var \Shockwavemk\Mail\Base\Model\Mail\Attachment $attachment */ 33 | /** @noinspection IsEmptyFunctionUsageInspection */ 34 | if(empty($attachment = $attachments[$localPath])) { 35 | 36 | $this->messageManager->addError( 37 | __('Sorry, there was an error getting requested content. Please contact the store owner.') 38 | ); 39 | 40 | return $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); 41 | } 42 | 43 | try 44 | { 45 | $this->_processDownload($attachment); 46 | exit(0); 47 | } 48 | catch (\Exception $e) 49 | { 50 | $this->messageManager->addError( 51 | __('Sorry, there was an error getting requested content. Please contact the store owner: ' . $e->getMessage()) 52 | ); 53 | } 54 | 55 | return $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); 56 | } 57 | 58 | /** 59 | * @param \Shockwavemk\Mail\Base\Model\Mail\Attachment $attachment 60 | */ 61 | protected function _processDownload($attachment) 62 | { 63 | /** @var \Magento\Framework\App\ResponseInterface $response */ 64 | $response = $this->getResponse(); 65 | 66 | $response->setHttpResponseCode( 67 | 200 68 | )->setHeader( 69 | 'Pragma', 70 | 'public', 71 | true 72 | )->setHeader( 73 | 'Cache-Control', 74 | 'must-revalidate, post-check=0, pre-check=0', 75 | true 76 | )->setHeader( 77 | 'Content-type', 78 | $attachment->getFileType(), 79 | true 80 | ); 81 | 82 | $this->getResponse()->setHeader('Content-Disposition', $attachment->getDisposition() . '; filename=' . basename($attachment->getFileName())); 83 | 84 | $this->getResponse()->clearBody(); 85 | $this->getResponse()->sendHeaders(); 86 | 87 | echo $attachment->getBinary(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Mail/Edit.php: -------------------------------------------------------------------------------- 1 | _request->getParam('id'); 19 | $this->_mail = $this->_objectManager->get('\Shockwavemk\Mail\Base\Model\Mail'); 20 | $this->_mail->load($mailId); 21 | 22 | $this->_view->loadLayout(); 23 | $this->_view->getPage()->getConfig()->getTitle()->prepend(__($this->_mail->getSubject())); 24 | $this->_view->renderLayout(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Mail/Overview.php: -------------------------------------------------------------------------------- 1 | _view->loadLayout(); 19 | $this->_view->getPage()->getConfig()->getTitle()->prepend(__('Transactional mails')); 20 | $this->_view->renderLayout(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Mail/Send.php: -------------------------------------------------------------------------------- 1 | _request->getParam('id'); 19 | $this->_mail = $this->_objectManager->get('\Shockwavemk\Mail\Base\Model\Mail'); 20 | $this->_mail->load($mailId); 21 | 22 | $this->_view->loadLayout(); 23 | $this->_view->getPage()->getConfig()->getTitle()->prepend(__($this->_mail->getSubject())); 24 | $this->_view->renderLayout(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Mail/SendPost.php: -------------------------------------------------------------------------------- 1 | transportBuilder = $transportBuilder; 39 | $this->storeManager = $storeManager; 40 | $this->customer = $customer; 41 | 42 | parent::__construct($context); 43 | } 44 | 45 | /** 46 | * Resend mail 47 | * 48 | * @return void|$this 49 | */ 50 | public function execute() 51 | { 52 | // Get request data 53 | $mailId = $this->_request->getParam('id'); 54 | $recalculate = $this->_request->getParam('resend_type'); 55 | 56 | // Email address to re-send mail 57 | $email = $this->_request->getParam('email'); 58 | 59 | /** @var \Shockwavemk\Mail\Base\Model\Mail $mail */ 60 | $mail = $this->_objectManager->get('Shockwavemk\Mail\Base\Model\Mail'); 61 | $mail->load($mailId); 62 | 63 | /** @noinspection IsEmptyFunctionUsageInspection */ 64 | if(empty($mailId) || empty($mail->getId())) { 65 | 66 | $redirectUrl = $this->_buildUrl( 67 | 'customer/mail/edit', 68 | ['_secure' => true, 'id' => $mailId] 69 | ); 70 | 71 | $this->messageManager->addException(new \Exception( 72 | __('Mail can not be loaded.')), 73 | __('Mail can not be loaded.') 74 | ); 75 | 76 | return $this->resultRedirectFactory 77 | ->create() 78 | ->setUrl( 79 | $this->_redirect->error($redirectUrl) 80 | ); 81 | } 82 | 83 | try 84 | { 85 | // Get information of parent mail 86 | 87 | /** @var MessageInterface $parentMessage */ 88 | $parentMessage = $mail->getMessage(); 89 | 90 | /** @var AttachmentInterface[] $parentAttachments */ 91 | $parentAttachments = $mail->getAttachments(); 92 | 93 | $parentCustomerId = $mail->getCustomerId(); 94 | 95 | // Set parent id to allow parent links in frontend 96 | $mail->setParentId( 97 | $mail->getId() 98 | ); 99 | 100 | // On given $email 101 | if(!empty($email)) { 102 | $recipients = [$email]; 103 | } else { 104 | $recipients = $mail->getRecipients(); 105 | } 106 | 107 | // Derive a transportBuilder from existing Mail 108 | $transportBuilder = $this->deriveTransportBuilderFromExistingMail($mail); 109 | 110 | $this->applyRecipientsOnTransportBuilder($recipients, $transportBuilder); 111 | 112 | 113 | /** @noinspection IsEmptyFunctionUsageInspection */ 114 | if(!empty($recalculate) && $recalculate === 'recalculate') { 115 | $transport = $transportBuilder->getTransport(); 116 | } else { 117 | $transport = $transportBuilder->getBackupTransport($parentMessage); 118 | } 119 | 120 | $transport->getMail() 121 | ->setAttachments($parentAttachments) 122 | ->setCustomerId($parentCustomerId) 123 | ; 124 | 125 | /** @var \Shockwavemk\Mail\Base\Model\Transports\TransportInterface $transport */ 126 | $transport->sendMessage(); 127 | 128 | $this->messageManager->addSuccess(__('Mail re-sent to customer.')); 129 | 130 | $url = $this->_buildUrl( 131 | 'customer/mail/edit', 132 | [ 133 | '_secure' => true, 134 | 'id' => $transport->getMail()->getId() 135 | ] 136 | ); 137 | 138 | return $this->resultRedirectFactory->create()->setUrl($this->_redirect->success($url)); 139 | 140 | } 141 | catch (InputException $e) 142 | { 143 | 144 | $this->messageManager->addError($e->getMessage()); 145 | 146 | foreach ($e->getErrors() as $error) { 147 | 148 | $this->messageManager->addError( 149 | $error->getMessage() 150 | ); 151 | 152 | } 153 | 154 | } 155 | catch (\Exception $e) 156 | { 157 | 158 | $this->messageManager->addException( 159 | $e, 160 | __($e->getMessage()) 161 | ); 162 | 163 | } 164 | 165 | $redirectUrl = $this->_buildUrl( 166 | 'customer/mail/send', 167 | ['_secure' => true, 'id' => $mailId] 168 | ); 169 | 170 | return $this->resultRedirectFactory->create()->setUrl( 171 | $this->_redirect->error($redirectUrl) 172 | ); 173 | } 174 | 175 | /** 176 | * @param string $route 177 | * @param array $params 178 | * @return string 179 | */ 180 | protected function _buildUrl($route = '', $params = []) 181 | { 182 | /** @var \Magento\Framework\UrlInterface $urlBuilder */ 183 | $urlBuilder = $this->_objectManager->create('Magento\Framework\UrlInterface'); 184 | return $urlBuilder->getUrl($route, $params); 185 | } 186 | 187 | /** 188 | * TODO 189 | * 190 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 191 | * 192 | * @return TransportBuilder 193 | * @throws \Magento\Framework\Exception\MailException 194 | */ 195 | protected function deriveTransportBuilderFromExistingMail($mail) 196 | { 197 | return $this->transportBuilder 198 | ->setTemplateIdentifier($mail->getTemplateIdentifier()) 199 | ->setTemplateOptions(['area' => Area::AREA_FRONTEND, 'store' => $mail->getStoreId()]) 200 | ->setTemplateVars($mail->getVars()) 201 | ->setFrom($mail->getSenderMail()); 202 | } 203 | 204 | /** 205 | * @param $recipients 206 | * @param $transportBuilder 207 | */ 208 | protected function applyRecipientsOnTransportBuilder($recipients, $transportBuilder) 209 | { 210 | // Some times magento does not save recipients as array 211 | if (is_array($recipients)) { 212 | foreach ($recipients as $recipient) { 213 | $transportBuilder->addTo( 214 | $recipient 215 | ); 216 | } 217 | } else { 218 | $transportBuilder->addTo( 219 | $recipients 220 | ); 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /Model/Config.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $scopeConfig; 72 | } 73 | 74 | // Transport 75 | 76 | /** 77 | * @return mixed 78 | */ 79 | public function getTransportType() 80 | { 81 | return $this->scopeConfig->getValue(self::XML_PATH_TYPE); 82 | } 83 | 84 | /** 85 | * @return mixed 86 | */ 87 | public function getTransportTypes() 88 | { 89 | return $this->scopeConfig->getValue(self::XML_PATH_TRANSPORT_TYPES); 90 | } 91 | 92 | /** 93 | * @return null 94 | */ 95 | public function getTransportClassName() 96 | { 97 | $className = null; 98 | 99 | $type = $this->getTransportType(); 100 | $types = $this->getTransportTypes(); 101 | 102 | if(!empty($types[$type])) 103 | { 104 | $typeConfig = $types[$type]; 105 | 106 | $className = $typeConfig['class']; 107 | } 108 | 109 | return $className; 110 | } 111 | 112 | // Storage 113 | 114 | /** 115 | * This path is used to store (temporary) files on local server 116 | * e.g. /pub/media/emails 117 | * 118 | * @return mixed 119 | */ 120 | public function getHostSpoolerFolderPath() 121 | { 122 | return $this->scopeConfig->getValue(self::XML_PATH_SPOOLER_FOLDER_PATH); 123 | } 124 | 125 | /** 126 | * A configurable limit how often a write access is tried on local file system on write 127 | * 128 | * @return mixed 129 | */ 130 | public function getHostRetryLimit() 131 | { 132 | return $this->scopeConfig->getValue(self::XML_PATH_SPOOLER_RETRY_LIMIT); 133 | } 134 | 135 | /** 136 | * Returns string class name of storage class 137 | * 138 | * @return string 139 | */ 140 | public function getStorageType() 141 | { 142 | return $this->scopeConfig->getValue(self::XML_PATH_STORAGE); 143 | } 144 | 145 | /** 146 | * @return string[] 147 | */ 148 | public function getStorageTypes() 149 | { 150 | return $this->scopeConfig->getValue(self::XML_PATH_STORAGE_TYPES); 151 | } 152 | 153 | /** 154 | * @return null 155 | */ 156 | public function getStorageClassName() 157 | { 158 | $type = $this->getStorageType(); 159 | $types = $this->getStorageTypes(); 160 | 161 | if(empty($typeConfig = $types[$type])) 162 | { 163 | return null; 164 | } 165 | 166 | return $typeConfig['class']; 167 | } 168 | 169 | /** 170 | * If test mode enabled, emails are built and stored, not sent 171 | * 172 | * @return bool 173 | */ 174 | public function getTestMode() 175 | { 176 | return $this->scopeConfig->getValue(self::XML_PATH_MAIL_TEST_MODE); 177 | } 178 | 179 | /** 180 | * Depending on mail transport vendor, all links in outgoing messages are replaced by tracking links 181 | * 182 | * @return bool 183 | */ 184 | public function getTrackingEnabled() 185 | { 186 | return $this->scopeConfig->getValue(self::XML_PATH_MAIL_TRACKING_ENABLED); 187 | } 188 | 189 | /** 190 | * @return bool 191 | */ 192 | public function getTrackingClicksEnabled() 193 | { 194 | return $this->scopeConfig->getValue(self::XML_PATH_MAIL_TRACKING_CLICKS_ENABLED); 195 | } 196 | 197 | /** 198 | * @return bool 199 | */ 200 | public function getTrackingOpensEnabled() 201 | { 202 | return $this->scopeConfig->getValue(self::XML_PATH_MAIL_TRACKING_OPENS_ENABLED); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /Model/Config/Source/Storage.php: -------------------------------------------------------------------------------- 1 | config = $config; 19 | } 20 | 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function toOptionArray() 25 | { 26 | $selection = array(); 27 | foreach ($this->config->getStorageTypes() as $storageType) 28 | { 29 | $selection[] = [ 30 | 'label' => __($storageType['label']), 31 | 'value' => $storageType['value'] 32 | ]; 33 | } 34 | 35 | return $selection; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Model/Config/Source/Type.php: -------------------------------------------------------------------------------- 1 | config = $config; 19 | } 20 | 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function toOptionArray() 25 | { 26 | if(empty($this->config->getTransportTypes())) 27 | { 28 | return [ 29 | ['label' => __('Disabled'), 'value' => 'disabled'] 30 | ]; 31 | } 32 | 33 | $selection = array(); 34 | foreach ($this->config->getTransportTypes() as $transportType) 35 | { 36 | $selection[] = ['label' => __($transportType['label']), 'value' => $transportType['value']]; 37 | } 38 | 39 | return $selection; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Model/Mail.php: -------------------------------------------------------------------------------- 1 | _storeManager = $storeManager; 172 | $this->_date = $date; 173 | $this->_mathRandom = $mathRandom; 174 | $this->_dateTime = $dateTime; 175 | $this->_manager = $manager; 176 | $this->_config = $config; 177 | $this->_storage = $storage; 178 | $this->_timeZone = $timezone; 179 | 180 | parent::__construct($context, $registry, $resource, $resourceCollection, $data); 181 | } 182 | 183 | /** 184 | * @return Storages\Base 185 | */ 186 | public function getstorage() 187 | { 188 | return $this->_storage; 189 | } 190 | 191 | /** 192 | * Every mailing is associated to a 'real' message 193 | * 194 | * @return \Shockwavemk\Mail\Base\Model\Mail\Message|null 195 | * @throws \Magento\Framework\Exception\MailException 196 | */ 197 | public function getMessage() 198 | { 199 | /** @noinspection IsEmptyFunctionUsageInspection */ 200 | /** @noinspection PhpUndefinedMethodInspection */ 201 | if (!empty(parent::getMessage())) { 202 | /** @noinspection PhpUndefinedMethodInspection */ 203 | return parent::getMessage(); 204 | } 205 | 206 | return $this->_storage->loadMessage( 207 | $this 208 | ); 209 | } 210 | 211 | /** 212 | * @param \Shockwavemk\Mail\Base\Model\Mail\AttachmentInterface $attachment 213 | * @throws \Magento\Framework\Exception\MailException 214 | */ 215 | public function addAttachment($attachment) 216 | { 217 | /** @var \Shockwavemk\Mail\Base\Model\Mail\AttachmentInterface[] $currentAttachments */ 218 | $attachments = $this->getAttachments(); 219 | $attachment->setMail($this); 220 | 221 | $attachments[$attachment->getFileName()] = $attachment; 222 | 223 | /** @noinspection PhpUndefinedMethodInspection */ 224 | $this->setAttachments($attachments); 225 | } 226 | 227 | /** 228 | * A mail can have zero to n associated attachments. 229 | * An Attachment is a meta description to get information and access to binary data 230 | * 231 | * @return \Shockwavemk\Mail\Base\Model\Mail\Attachment[] Attachments 232 | * @throws \Magento\Framework\Exception\MailException 233 | */ 234 | public function getAttachments() 235 | { 236 | /** @noinspection IsEmptyFunctionUsageInspection */ 237 | /** @noinspection PhpUndefinedMethodInspection */ 238 | if (!empty(parent::getAttachments())) { 239 | /** @noinspection PhpUndefinedMethodInspection */ 240 | return parent::getAttachments(); 241 | } 242 | 243 | $attachments = $this->_storage->getAttachments($this); 244 | 245 | /** @noinspection PhpUndefinedMethodInspection */ 246 | $this->setAttachments($attachments); 247 | 248 | return $attachments; 249 | } 250 | 251 | /** 252 | * Get the recipients as string array 253 | * Recipients are stored as json in database 254 | * 255 | * @return string[] 256 | */ 257 | public function getRecipients() 258 | { 259 | $value = $this->getData('recipients'); 260 | return json_decode($value, true); 261 | } 262 | 263 | /** 264 | * @param array $value 265 | * @return \Shockwavemk\Mail\Base\Model\Mail 266 | */ 267 | public function setSenderMail(array $value) 268 | { 269 | $value = json_encode($value); 270 | $this->setData('sender_mail', $value); 271 | return $this; 272 | } 273 | 274 | /** 275 | * @return mixed 276 | */ 277 | public function getSenderMail() 278 | { 279 | $value = $this->getData('sender_mail'); 280 | /** @noinspection IsEmptyFunctionUsageInspection */ 281 | if (!empty($decoded = json_decode($value, true))) { 282 | return $decoded; 283 | } 284 | return ''; 285 | } 286 | 287 | /** 288 | * @param array $value 289 | * @return $this 290 | */ 291 | public function setVars(array $value) 292 | { 293 | /** @noinspection IsEmptyFunctionUsageInspection */ 294 | if (!empty($value)) { 295 | $value = $this->convertMagentoModelsToPointer($value); 296 | } 297 | 298 | $value = json_encode($value); 299 | $this->setData('vars', $value); 300 | return $this; 301 | } 302 | 303 | /** 304 | * @param $mailValues 305 | * @return array 306 | */ 307 | protected function convertMagentoModelsToPointer($mailValues) 308 | { 309 | $newMailValues = []; 310 | 311 | foreach ($mailValues as $key => $value) { 312 | if (is_subclass_of($value, 'Magento\Framework\Model\AbstractModel')) { 313 | /** @var \Magento\Framework\Model\AbstractModel $value */ 314 | $value = array( 315 | 'entity_id' => $value->getId(), 316 | 'class' => get_class($value) 317 | ); 318 | } 319 | /** @noinspection IsEmptyFunctionUsageInspection */ 320 | elseif(method_exists($value, 'getData') && !empty($value->getData())) { 321 | $valueData = $value->getData(); 322 | 323 | /** @noinspection IsEmptyFunctionUsageInspection */ 324 | if(method_exists($value, 'getId') && !empty($value->getId())) { 325 | $valueData['entity_id'] = $value->getId(); 326 | } 327 | 328 | $valueData['class'] = get_class($value); 329 | $value = $valueData; 330 | } 331 | 332 | $newMailValues[$key] = $value; 333 | } 334 | 335 | return $newMailValues; 336 | } 337 | 338 | /** 339 | * @return array|mixed 340 | */ 341 | public function getVars() 342 | { 343 | $value = $this->getData('vars'); 344 | 345 | /** @noinspection IsEmptyFunctionUsageInspection */ 346 | if (!empty($value)) { 347 | $value = json_decode($value, true); 348 | $value = $this->convertPointerToMagentoModels($value); 349 | } 350 | 351 | return $value; 352 | } 353 | 354 | /** 355 | * Recover magento models from stored class/ids tuple 356 | * 357 | * @param $mailValues 358 | * @return \Magento\Framework\Model\AbstractModel[]|array 359 | * @throws \Magento\Framework\Exception\LocalizedException 360 | */ 361 | protected function convertPointerToMagentoModels($mailValues) 362 | { 363 | $newMailValues = []; 364 | 365 | foreach ($mailValues as $key => $value) { 366 | if (is_array($value) 367 | && !empty($value['class']) && !empty($value['entity_id']) 368 | ) { 369 | $newMailValues[$key] = $this->createAbstractModelFromValue($value); 370 | } 371 | /** @noinspection NotOptimalIfConditionsInspection */ 372 | elseif(is_array($value) && !empty($value['class'])) { 373 | $newMailValues[$key] = $this->createObjectFromValue($value); 374 | } 375 | else { 376 | $newMailValues[$key] = $value; 377 | } 378 | } 379 | 380 | return $newMailValues; 381 | } 382 | 383 | /** 384 | * Options are stored as json string in database 385 | * 386 | * @param array $value 387 | * @return $this 388 | */ 389 | public function setOptions(array $value) 390 | { 391 | $value = json_encode($value); 392 | $this->setData('options', $value); 393 | return $this; 394 | } 395 | 396 | /** 397 | * Options are stored as json string in database 398 | * 399 | * @return mixed 400 | */ 401 | public function getOptions() 402 | { 403 | $value = $this->getData('options'); 404 | return json_decode($value, true); 405 | } 406 | 407 | /** 408 | * Set tags used for mail 409 | * Tags are stored as json string in database 410 | * 411 | * @param $value 412 | * @return $this 413 | */ 414 | public function setTags($value) 415 | { 416 | $value = json_encode($value); 417 | $this->setData('tags', $value); 418 | return $this; 419 | } 420 | 421 | /** 422 | * Get tags used for mail 423 | * Tags are stored as json string in database 424 | * 425 | * @return mixed 426 | */ 427 | public function getTags() 428 | { 429 | $value = $this->getData('tags'); 430 | /** @noinspection IsEmptyFunctionUsageInspection */ 431 | if (!empty($decoded = json_decode($value, true))) { 432 | return $decoded; 433 | } 434 | return []; 435 | } 436 | 437 | /** 438 | * Update mail data with information created while transport init 439 | * 440 | * @param \Shockwavemk\Mail\Base\Model\Transports\TransportInterface $transport 441 | * @return \Shockwavemk\Mail\Base\Model\Mail 442 | */ 443 | public function updateWithTransport($transport) 444 | { 445 | /** @noinspection PhpUndefinedMethodInspection */ 446 | /** @noinspection PhpUndefinedMethodInspection */ 447 | $this 448 | ->setSubject($transport->getMessage()->getSubject()) 449 | ->setMessage($transport->getMessage()) 450 | ->setRecipients($transport->getMessage()->getRecipients()) 451 | ->setId(null); 452 | 453 | return $this; 454 | } 455 | 456 | /** 457 | * Set recipients of mail 458 | * 459 | * @param string[] $value 460 | * @return \Shockwavemk\Mail\Base\Model\Mail 461 | */ 462 | public function setRecipients(array $value) 463 | { 464 | $value = json_encode($value); 465 | $this->setData('recipients', $value); 466 | return $this; 467 | } 468 | 469 | /** 470 | * Set reverse link on each attached entity 471 | * 472 | * @var $attachments AttachmentInterface[] 473 | */ 474 | public function setAttachments($attachments) { 475 | /** @var AttachmentInterface $attachment */ 476 | foreach ($attachments as $attachment) { 477 | $attachment->setMail($this); 478 | } 479 | 480 | $this->setData('attachments', $attachments); 481 | 482 | return $this; 483 | } 484 | 485 | /** 486 | * Save mail to db, message and attachments to storage 487 | * 488 | * @throws \Magento\Framework\Exception\MailException 489 | * @throws \Exception 490 | */ 491 | public function save() 492 | { 493 | parent::save(); 494 | 495 | $this->_storage->saveMessage($this); 496 | 497 | $this->_storage->saveAttachments($this); 498 | 499 | $this->_storage->saveMail($this); 500 | } 501 | 502 | /** 503 | * Specify data which should be serialized to JSON 504 | * @link http://php.net/manual/en/jsonserializable.jsonserialize.php 505 | * @return mixed data which can be serialized by json_encode, 506 | * @throws \Magento\Framework\Exception\MailException 507 | * which is a value of any type other than a resource. 508 | * @since 5.4.0 509 | */ 510 | public function jsonSerialize() 511 | { 512 | $data = $this->getData(); 513 | $attachmentsSerialized = []; 514 | 515 | /** @noinspection IsEmptyFunctionUsageInspection */ 516 | if (!empty($this->getAttachments())) { 517 | foreach ($this->getAttachments() as $attachment) { 518 | $attachmentsSerialized[] = $attachment->jsonSerialize(); 519 | } 520 | } 521 | 522 | $data['attachments'] = $attachmentsSerialized; 523 | 524 | return $data; 525 | } 526 | 527 | /** 528 | * @return string 529 | */ 530 | public function getDeliveryTime() 531 | { 532 | $deliveryTime = $this->getData('delivery_time'); 533 | $dateTime = $this->_timeZone->date($deliveryTime); 534 | 535 | return $dateTime->format('D, d M Y H:i:s O'); 536 | } 537 | 538 | /** 539 | * @return string 540 | */ 541 | public function getSentAt() 542 | { 543 | $sentAt = $this->getData('sent_at'); 544 | $dateTime = $this->_timeZone->date($sentAt); 545 | 546 | return $dateTime->format('D, d M Y H:i:s O'); 547 | } 548 | 549 | /** 550 | * @return string 551 | */ 552 | public function getTestMode() 553 | { 554 | /** @noinspection IsEmptyFunctionUsageInspection */ 555 | if (empty($value = $this->getData('test_mode'))) { 556 | $value = $this->_config->getTestMode(); 557 | $this->setData('test_mode', $value); 558 | } 559 | 560 | return $value; 561 | } 562 | 563 | /** 564 | * @return string 565 | */ 566 | public function getTrackingEnabled() 567 | { 568 | /** @noinspection IsEmptyFunctionUsageInspection */ 569 | if (empty($value = $this->getData('tracking_enabled'))) { 570 | $value = $this->_config->getTrackingEnabled(); 571 | $this->setData('tracking_enabled', $value); 572 | } 573 | 574 | return $value; 575 | } 576 | 577 | /** 578 | * @return string 579 | */ 580 | public function getTrackingClicksEnabled() 581 | { 582 | /** @noinspection IsEmptyFunctionUsageInspection */ 583 | if (empty($value = $this->getData('tracking_clicks_enabled'))) { 584 | $value = $this->_config->getTrackingClicksEnabled(); 585 | $this->setData('tracking_clicks_enabled', $value); 586 | } 587 | 588 | return $value; 589 | } 590 | 591 | /** 592 | * @return string 593 | */ 594 | public function getTrackingOpensEnabled() 595 | { 596 | /** @noinspection IsEmptyFunctionUsageInspection */ 597 | if (empty($value = $this->getData('tracking_opens_enabled'))) { 598 | $value = $this->_config->getTrackingOpensEnabled(); 599 | $this->setData('tracking_opens_enabled', $value); 600 | } 601 | 602 | return $value; 603 | } 604 | 605 | /** 606 | * @param array $items 607 | * @param \Magento\Eav\Model\Entity\Collection\AbstractCollection $collection 608 | * @return \Magento\Eav\Model\Entity\Collection\AbstractCollection 609 | * @throws \Magento\Framework\Exception\LocalizedException 610 | */ 611 | protected function convertValueArrayToCollectionItems($items, $collection) 612 | { 613 | /** @var \Magento\Rma\Model\ResourceModel\Item\Collection $collection */ 614 | foreach ($items as $item) { 615 | $collection->addFilter('entity_id', $item['entity_id'], 'or'); 616 | } 617 | 618 | $collection->load(); 619 | 620 | return $collection; 621 | } 622 | 623 | /** 624 | * @param $value 625 | * @return mixed 626 | */ 627 | protected function createObjectFromValue($value) 628 | { 629 | $object = $this->_manager->get($value['class']); 630 | unset($value['class']); 631 | 632 | // if object is a collection 633 | if (!empty($value) 634 | && is_subclass_of($object, '\Magento\Eav\Model\Entity\Collection\AbstractCollection') 635 | ) { 636 | $this->convertValueArrayToCollectionItems($value, $object); 637 | return $object; 638 | } 639 | return $object; 640 | } 641 | 642 | /** 643 | * @param $value 644 | * @return mixed 645 | */ 646 | protected function createAbstractModelFromValue($value) 647 | { 648 | /** @var \Magento\Framework\Model\AbstractModel $value */ 649 | $newValue = $this->_manager->get($value['class']); 650 | $newValue->load($value['entity_id']); 651 | return $newValue; 652 | } 653 | } 654 | -------------------------------------------------------------------------------- /Model/Mail/Attachment.php: -------------------------------------------------------------------------------- 1 | storage = $storage; 60 | } 61 | 62 | /** 63 | * Return all data, except binary 64 | * 65 | * @return array 66 | */ 67 | public function jsonSerialize() 68 | { 69 | $data = $this->getData(); 70 | $data['binary'] = ''; 71 | $data['mail'] = $this->getMail()->getId(); 72 | 73 | return $data; 74 | } 75 | 76 | /** 77 | * Returns a mime part converted attachment 78 | * 79 | * @return Zend_Mime_Part 80 | */ 81 | public function toMimePart() 82 | { 83 | /** @var Zend_Mime_Part $attachmentMimePart */ 84 | $attachmentMimePart = new Zend_Mime_Part($this->getBinary()); 85 | 86 | $attachmentMimePart->type = $this->getMimeType(); 87 | $attachmentMimePart->disposition = $this->getDisposition(); 88 | $attachmentMimePart->encoding = $this->getEncoding(); 89 | $attachmentMimePart->filename = $this->getFileName(); 90 | 91 | return $attachmentMimePart; 92 | } 93 | 94 | /** 95 | * Returns binary data of a single attachment file 96 | * 97 | * @return string binary data of attachment file 98 | * @throws \Magento\Framework\Exception\MailException 99 | */ 100 | public function getBinary() 101 | { 102 | /** @noinspection IsEmptyFunctionUsageInspection */ 103 | if (empty($this->getData('binary'))) { 104 | 105 | $attachmentBinary = file_get_contents( 106 | $this->getFilePath() 107 | ); 108 | 109 | $this->setData( 110 | 'binary', 111 | $attachmentBinary 112 | ); 113 | } 114 | 115 | return $this->getData('binary'); 116 | } 117 | 118 | public function getMail() 119 | { 120 | return $this->getData('mail'); 121 | } 122 | 123 | /** 124 | * 125 | * 126 | * @return string 127 | */ 128 | public function getFilePath() 129 | { 130 | /** @noinspection IsEmptyFunctionUsageInspection */ 131 | if (empty($this->getData('file_path'))) { 132 | $this->setData('file_path', 133 | self::ZEND_MAIL_ATTACHMENT_DEFAULT_FILENAME 134 | ); 135 | } 136 | 137 | return $this->getData('file_path'); 138 | } 139 | 140 | /** 141 | * 142 | * 143 | * @return string 144 | */ 145 | public function getMimeType() 146 | { 147 | /** @noinspection IsEmptyFunctionUsageInspection */ 148 | if (empty($this->getData('mime_type'))) { 149 | $this->setData('mime_type', 150 | self::ZEND_MAIL_ATTACHMENT_DEFAULT_TYPE 151 | ); 152 | } 153 | 154 | return $this->getData('mime_type'); 155 | } 156 | 157 | /** 158 | * Returns current setting of how to integrate attachment into message 159 | * 160 | * @return string 161 | */ 162 | public function getDisposition() 163 | { 164 | /** @noinspection IsEmptyFunctionUsageInspection */ 165 | if (empty($this->getData('disposition'))) { 166 | $this->setData( 167 | 'disposition', 168 | Zend_Mime::DISPOSITION_ATTACHMENT 169 | ); 170 | } 171 | 172 | return $this->getData('disposition'); 173 | } 174 | 175 | public function getEncoding() 176 | { 177 | /** @noinspection IsEmptyFunctionUsageInspection */ 178 | if (empty($this->getData('encoding'))) { 179 | $this->setData( 180 | 'encoding', 181 | Zend_Mime::ENCODING_BASE64 182 | ); 183 | } 184 | return $this->getData('encoding'); 185 | } 186 | 187 | /** 188 | * 189 | * 190 | * @return string 191 | */ 192 | public function getFileName() 193 | { 194 | if (empty($this->getData('file_name'))) { 195 | return basename($this->getFilePath()); 196 | } 197 | 198 | return $this->getData('file_name'); 199 | } 200 | 201 | /** 202 | * @param string $value 203 | * @return $this 204 | */ 205 | public function setFileName($value) 206 | { 207 | $this->setData('file_name', $value); 208 | return $this; 209 | } 210 | 211 | /** 212 | * @param string 213 | * @return $this 214 | */ 215 | public function setBinary($value) 216 | { 217 | $this->setData('binary', $value); 218 | return $this; 219 | } 220 | 221 | /** 222 | * @param string $value 223 | * @return $this 224 | */ 225 | public function setDisposition($value) 226 | { 227 | $this->setData('disposition', $value); 228 | return $this; 229 | } 230 | 231 | /** 232 | * @param string $value 233 | * @return $this 234 | */ 235 | public function setEncoding($value) 236 | { 237 | $this->setData('encoding', $value); 238 | return $this; 239 | } 240 | 241 | /** 242 | * @param string $value 243 | * @return $this 244 | */ 245 | public function setHash($value) 246 | { 247 | $this->setData('hash', $value); 248 | return $this; 249 | } 250 | 251 | /** 252 | * @param string $value 253 | * @return $this 254 | */ 255 | public function setMimeType($value) 256 | { 257 | $this->setData('mime_type', $value); 258 | return $this; 259 | } 260 | 261 | /** 262 | * @param string $value 263 | * @return $this 264 | */ 265 | public function setFilePath($value) 266 | { 267 | $this->setData('file_path', $value); 268 | return $this; 269 | } 270 | 271 | /** 272 | * @param string $value 273 | * @return $this 274 | */ 275 | public function setSize($value) 276 | { 277 | $this->setData('size', $value); 278 | return $this; 279 | } 280 | 281 | /** 282 | * @param string $value 283 | * @return $this 284 | */ 285 | public function setUrl($value) 286 | { 287 | $this->setData('url', $value); 288 | return $this; 289 | } 290 | 291 | /** 292 | * @param string 293 | * @return $this 294 | */ 295 | public function setMail($value) 296 | { 297 | $this->setData('mail', $value); 298 | return $this; 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /Model/Mail/AttachmentCollection.php: -------------------------------------------------------------------------------- 1 | json_encode, 16 | * which is a value of any type other than a resource. 17 | * @since 5.4.0 18 | */ 19 | public function jsonSerialize() 20 | { 21 | return array( 22 | 'type' => $this->getType(), 23 | 'bodyText' => $this->getBodyText(), 24 | 'bodyHtml' => $this->getBodyHtml(), 25 | 'recipients' => $this->getRecipients(), 26 | 'from' => $this->getFrom(), 27 | 'subject' => $this->getSubject(), 28 | 'text' => quoted_printable_decode($this->getBodyText(true)), 29 | 'html' => quoted_printable_decode($this->getBodyHtml(true)) 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Model/Mail/MessageInterface.php: -------------------------------------------------------------------------------- 1 | _init('shockwavemk_mail_entity', 'entity_id'); 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function save(\Magento\Framework\Model\AbstractModel $object) 38 | { 39 | $object->setHasDataChanges(true); 40 | return parent::save($object); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Model/ResourceModel/Mail/Collection.php: -------------------------------------------------------------------------------- 1 | _objectManager = \Magento\Framework\App\ObjectManager::getInstance(); 23 | parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $connection, $resource); 24 | } 25 | 26 | /** 27 | * Initialize resource 28 | * 29 | * @return void 30 | */ 31 | protected function _construct() 32 | { 33 | $this->_init('Shockwavemk\Mail\Base\Model\Mail', 'Shockwavemk\Mail\Base\Model\ResourceModel\Mail'); 34 | } 35 | 36 | /** 37 | * Init collection select 38 | * 39 | * @return $this 40 | */ 41 | protected function _initSelect() 42 | { 43 | $request = $this->_objectManager->get('Magento\Framework\App\RequestInterface'); 44 | $this->getSelect()->from(['main_table' => $this->getMainTable()]); 45 | 46 | if(!empty($customerId = $request->getParam('id'))) { 47 | $this->getSelect()->where('customer_id = ?', $customerId); 48 | } 49 | 50 | return $this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Model/Simulation/Config.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $scopeConfig; 31 | } 32 | 33 | public function getValue($path, $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null) 34 | { 35 | return $this->scopeConfig->getValue($path, $scopeType, $scopeCode); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Model/Storages/Base.php: -------------------------------------------------------------------------------- 1 | _storage = $manager->get( 32 | $config->getStorageClassName() 33 | ); 34 | 35 | } catch(\Exception $e) { 36 | 37 | throw new MailException( 38 | new Phrase($e->getMessage()), 39 | $e) 40 | ; 41 | 42 | } 43 | } 44 | 45 | /** 46 | * Send a mail using this transport 47 | * 48 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 49 | * @return $id 50 | * @throws MailException 51 | * 52 | */ 53 | public function saveMessage($mail) 54 | { 55 | try { 56 | 57 | return $this->_storage->saveMessage($mail); 58 | 59 | } catch (\Exception $e) { 60 | 61 | throw new MailException( 62 | new Phrase($e->getMessage()), 63 | $e) 64 | ; 65 | 66 | } 67 | } 68 | 69 | /** 70 | * Send a mail using this transport 71 | * 72 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 73 | * 74 | * @return \Shockwavemk\Mail\Base\Model\Mail\Message 75 | * @throws MailException 76 | */ 77 | public function loadMessage($mail) 78 | { 79 | try { 80 | 81 | return $this->_storage->loadMessage($mail); 82 | 83 | } catch (\Exception $e) { 84 | 85 | throw new MailException( 86 | new Phrase($e->getMessage()), 87 | $e) 88 | ; 89 | 90 | } 91 | } 92 | 93 | /** 94 | * TODO 95 | * 96 | * @param AttachmentInterface $attachment 97 | * @return string $id 98 | * @throws MailException 99 | */ 100 | public function saveAttachment($attachment) 101 | { 102 | try { 103 | 104 | return $this->_storage->saveAttachment($attachment); 105 | 106 | } catch (\Exception $e) { 107 | 108 | throw new MailException( 109 | new Phrase($e->getMessage()), 110 | $e) 111 | ; 112 | 113 | } 114 | } 115 | 116 | /** 117 | * TODO 118 | * 119 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 120 | * 121 | * @return \Shockwavemk\Mail\Base\Model\Mail\AttachmentInterface[] 122 | * @throws MailException 123 | */ 124 | public function getAttachments($mail) 125 | { 126 | try { 127 | 128 | return $this->_storage->getAttachments($mail); 129 | 130 | } catch (\Exception $e) { 131 | 132 | throw new MailException( 133 | new Phrase($e->getMessage()), 134 | $e) 135 | ; 136 | 137 | } 138 | } 139 | 140 | /** 141 | * TODO 142 | * 143 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 144 | * @return $this 145 | * @throws MailException 146 | */ 147 | public function saveAttachments($mail) 148 | { 149 | try { 150 | return $this->_storage->saveAttachments($mail); 151 | 152 | } catch (\Exception $e) { 153 | 154 | throw new MailException( 155 | new Phrase($e->getMessage()), 156 | $e) 157 | ; 158 | 159 | } 160 | } 161 | 162 | /** 163 | * Use the concrete storage implementation to save the mail to storage 164 | * 165 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 166 | * 167 | * @return $id 168 | * @throws MailException 169 | */ 170 | public function saveMail($mail) 171 | { 172 | try { 173 | 174 | return $this->_storage->saveMail($mail); 175 | 176 | } catch (\Exception $e) { 177 | 178 | throw new MailException( 179 | new Phrase($e->getMessage()), 180 | $e) 181 | ; 182 | 183 | } 184 | } 185 | 186 | /** 187 | * Use the concrete storage implementation to load the mail to storage 188 | * 189 | * @return \Shockwavemk\Mail\Base\Model\Mail 190 | * @throws MailException 191 | */ 192 | public function loadMail($id) 193 | { 194 | try { 195 | 196 | return $this->_storage->loadMail($id); 197 | 198 | } catch (\Exception $e) { 199 | 200 | throw new MailException( 201 | new Phrase($e->getMessage()), 202 | $e) 203 | ; 204 | 205 | } 206 | } 207 | 208 | /** 209 | * TODO 210 | * 211 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 212 | * @param string $path 213 | * @return AttachmentInterface 214 | * @throws MailException 215 | */ 216 | public function loadAttachment($mail, $path) 217 | { 218 | try { 219 | 220 | return $this->_storage->loadAttachment($mail, $path); 221 | 222 | } catch (\Exception $e) { 223 | 224 | throw new MailException( 225 | new Phrase($e->getMessage()), 226 | $e) 227 | ; 228 | 229 | } 230 | } 231 | 232 | /** 233 | * TODO 234 | * 235 | * @return string 236 | * @throws MailException 237 | */ 238 | public function getMailFolderPathById($mailId) 239 | { 240 | try { 241 | 242 | return $this->_storage->getMailFolderPathById($mailId); 243 | 244 | } catch (\Exception $e) { 245 | 246 | throw new MailException( 247 | new Phrase($e->getMessage()), 248 | $e) 249 | ; 250 | 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /Model/Storages/DefaultStorage.php: -------------------------------------------------------------------------------- 1 | _config = $config; 50 | $this->_objectManager = $objectManager; 51 | } 52 | 53 | /** 54 | * Save file to spool path 55 | * 56 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 57 | * @return $this 58 | * @throws \Magento\Framework\Exception\MailException 59 | * @throws \Magento\Framework\Exception\FileSystemException 60 | * @throws \Exception 61 | */ 62 | public function saveMessage($mail) 63 | { 64 | // convert message to html 65 | $messageHtml = quoted_printable_decode( 66 | $mail->getMessage()->getBodyHtml(true) 67 | ); 68 | 69 | $this->createFolder( 70 | $this->getMailFolderPathById($mail->getId()) 71 | ); 72 | 73 | // try to store message to filesystem 74 | $this->storeFile( 75 | $messageHtml, 76 | $this->getMailFolderPathById($mail->getId()) . 77 | DIRECTORY_SEPARATOR . self::MESSAGE_HTML_FILE_NAME 78 | ); 79 | 80 | // convert message to json 81 | $messageJson = json_encode($mail->getMessage()); 82 | 83 | // try to store message to filesystem 84 | $this->storeFile( 85 | $messageJson, 86 | $this->getMailFolderPathById($mail->getId()) . 87 | DIRECTORY_SEPARATOR . self::MESSAGE_FILE_NAME 88 | ); 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * Create a folder path, if folder does not exist 95 | * 96 | * @param $folderPath 97 | * @return $this 98 | * @throws \Magento\Framework\Exception\FileSystemException 99 | */ 100 | protected function createFolder($folderPath) 101 | { 102 | if(!@mkdir($folderPath,0777,true) && !is_dir($folderPath)) 103 | { 104 | throw new FileSystemException( 105 | new Phrase('Folder can not be created, but does not exist.') 106 | ); 107 | } 108 | 109 | return $this; 110 | } 111 | 112 | /** 113 | * Returns a string concat of magento config value for email spool path and mail id 114 | * e.g. pub/media/emails/ 115 | * 116 | * @param $mailId 117 | * @return string e.g. pub/media/emails/ 118 | */ 119 | public function getMailFolderPathById($mailId) 120 | { 121 | // store message in temporary file system spooler 122 | $hostTempFolderPath = $this->_config->getHostSpoolerFolderPath(); 123 | 124 | return $hostTempFolderPath . DIRECTORY_SEPARATOR . $mailId; 125 | } 126 | 127 | /** 128 | * Stores string data at a given path 129 | * 130 | * @param $data 131 | * @param $filePath 132 | * @return bool 133 | * @throws \Magento\Framework\Exception\FileSystemException 134 | * @throws \Exception 135 | */ 136 | protected function storeFile($data, $filePath) 137 | { 138 | // create a folder for message if needed 139 | if (!is_dir(dirname($filePath))) { 140 | $this->createFolder(dirname($filePath)); 141 | } 142 | 143 | /** @noinspection LoopWhichDoesNotLoopInspection */ 144 | for ($i = 0; $i < $this->_config->getHostRetryLimit(); $i++) { 145 | /* We try an exclusive creation of the file. 146 | * This is an atomic operation, it avoid locking mechanism 147 | */ 148 | $fp = @fopen($filePath, 'x'); 149 | 150 | if($fp !== false) { 151 | if (false === fwrite($fp, $data)) { 152 | return false; 153 | } 154 | 155 | fclose($fp); 156 | 157 | return $filePath; 158 | } 159 | } 160 | 161 | throw new FileSystemException( 162 | new Phrase('Unable to create a file for enqueuing Message') 163 | ); 164 | } 165 | 166 | /** 167 | * Restore a message from filesystem 168 | * 169 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 170 | * @return \Shockwavemk\Mail\Base\Model\Mail\Message $message 171 | * @throws \Exception 172 | * @throws \Zend_Mail_Exception 173 | */ 174 | public function loadMessage($mail) 175 | { 176 | $localFilePath = $this->getMailFolderPathById($mail->getId()) . 177 | DIRECTORY_SEPARATOR . self::MESSAGE_FILE_NAME; 178 | 179 | $messageJson = $this->restoreFile($localFilePath); 180 | 181 | if (empty($messageJson)) { 182 | $messageJson = '{}'; 183 | } 184 | 185 | $messageData = json_decode($messageJson); 186 | 187 | /** @var \Shockwavemk\Mail\Base\Model\Mail\Message $message */ 188 | $message = $this->_objectManager->create('Shockwavemk\Mail\Base\Model\Mail\Message'); 189 | 190 | if (!empty($messageData->type)) { 191 | $message->setType($messageData->type); 192 | } 193 | 194 | if (!empty($messageData->txt)) { 195 | $message->setBodyText($messageData->txt); 196 | } 197 | 198 | if (!empty($messageData->html)) { 199 | $message->setBodyHtml($messageData->html); 200 | } 201 | 202 | if (!empty($messageData->from)) { 203 | $message->setFrom($messageData->from); 204 | } 205 | 206 | if (!empty($messageData->subject)) { 207 | $message->setSubject($messageData->subject); 208 | } 209 | 210 | if (!empty($messageData->recipients)) { 211 | foreach ($messageData->recipients as $recipient) { 212 | $message->addTo($recipient); 213 | } 214 | } 215 | 216 | return $message; 217 | } 218 | 219 | /** 220 | * Load binary file data from a given file path 221 | * 222 | * @param $filePath 223 | * @return null|string 224 | */ 225 | private function restoreFile($filePath) 226 | { 227 | try { 228 | for ($i = 0; $i < $this->_config->getHostRetryLimit(); ++$i) { 229 | /* We try an exclusive creation of the file. This is an atomic operation, it avoid locking mechanism */ 230 | @fopen($filePath, 'x'); 231 | 232 | if (false === $fileData = file_get_contents($filePath)) { 233 | return null; 234 | } 235 | return $fileData; 236 | } 237 | } catch (\Exception $e) { 238 | return null; 239 | } 240 | 241 | return null; 242 | } 243 | 244 | /** 245 | * Convert a mail object to json and store it at mail folder path 246 | * 247 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 248 | * @return bool 249 | * @throws \Magento\Framework\Exception\FileSystemException 250 | * @throws \Exception 251 | */ 252 | public function saveMail($mail) 253 | { 254 | // first save file to spool path 255 | // to avoid exceptions on external storage provider connection 256 | 257 | // convert message to json 258 | $mailJson = json_encode($mail); 259 | 260 | $this->createFolder( 261 | $this->getMailFolderPathById($mail->getId()) 262 | ); 263 | 264 | // try to store message to filesystem 265 | return $this->storeFile( 266 | $mailJson, 267 | $this->getMailFolderPathById($mail->getId()) . 268 | DIRECTORY_SEPARATOR . self::MAIL_FILE_NAME 269 | ); 270 | } 271 | 272 | /** 273 | * Loads json file from storage and converts/parses it to a new mail object 274 | * 275 | * @param int $id folder name / id eg. pub/media/emails/66 276 | * @return \Shockwavemk\Mail\Base\Model\Mail 277 | * @throws \Exception 278 | */ 279 | public function loadMail($id) 280 | { 281 | /** @var \Shockwavemk\Mail\Base\Model\Mail $mail */ 282 | $mail = $this->_objectManager->create('Shockwavemk\Mail\Base\Model\Mail'); 283 | 284 | $mail->setId($id); 285 | 286 | $localFilePath = $this->getMailFolderPathById( 287 | $mail->getId()) . 288 | DIRECTORY_SEPARATOR . self::MAIL_FILE_NAME; 289 | 290 | $mailJson = $this->restoreFile($localFilePath); 291 | 292 | /** @noinspection IsEmptyFunctionUsageInspection */ 293 | if (empty($mailJson)) { 294 | $mailJson = '{}'; 295 | } 296 | 297 | /** @noinspection IsEmptyFunctionUsageInspection */ 298 | if (!empty($mailData = json_decode($mailJson, true))) { 299 | foreach ($mailData as $key => $value) { 300 | $mail->setData($key, $value); 301 | } 302 | } 303 | 304 | return $mail; 305 | } 306 | 307 | /** 308 | * Load binary data from storage provider 309 | * 310 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 311 | * @param string $path 312 | * @return string 313 | */ 314 | public function loadAttachment($mail, $path) 315 | { 316 | $attachmentFolder = DIRECTORY_SEPARATOR . self::ATTACHMENT_PATH; 317 | 318 | $localFilePath = $this->getMailFolderPathById($mail->getId()) . 319 | $attachmentFolder . $path; 320 | 321 | return $this->restoreFile($localFilePath); 322 | } 323 | 324 | /** 325 | * Returns attachments for a given mail 326 | * 327 | * \Shockwavemk\Mail\Base\Model\Mail $mail 328 | * 329 | * @return \Shockwavemk\Mail\Base\Model\Mail\Attachment[] 330 | */ 331 | public function getAttachments($mail) 332 | { 333 | /** @noinspection IsEmptyFunctionUsageInspection */ 334 | if(empty($mail->getId())) { 335 | return []; 336 | } 337 | 338 | $folderFileList = $this->getMailLocalFolderFileList($mail); 339 | 340 | $attachments = []; 341 | 342 | foreach ($folderFileList as $filePath => $fileMetaData) { 343 | 344 | $filePath = $fileMetaData['path']; 345 | 346 | /** @var \Shockwavemk\Mail\Base\Model\Mail\Attachment $attachment */ 347 | $attachment = $this->_objectManager 348 | ->create('\Shockwavemk\Mail\Base\Model\Mail\Attachment'); 349 | 350 | $attachment->setFilePath($filePath); 351 | $attachment->setMail($mail); 352 | 353 | // transfer all meta data into attachment object 354 | foreach ($fileMetaData as $attributeKey => $attributeValue) { 355 | $attachment->setData($attributeKey, $attributeValue); 356 | } 357 | 358 | $attachments[$filePath] = $attachment; 359 | } 360 | 361 | return $attachments; 362 | } 363 | 364 | /** 365 | * Get file list for a given mail 366 | * 367 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 368 | * @return array 369 | */ 370 | protected function getMailLocalFolderFileList($mail) 371 | { 372 | $spoolFolder = $this->_config->getHostSpoolerFolderPath() . 373 | DIRECTORY_SEPARATOR . 374 | $mail->getId() . 375 | DIRECTORY_SEPARATOR . 376 | self::ATTACHMENT_PATH; 377 | 378 | // create a folder for attachments if needed 379 | if (!is_dir($spoolFolder)) { 380 | $this->createFolder($spoolFolder); 381 | } 382 | 383 | /** @var RecursiveIteratorIterator $objects */ 384 | $objects = new RecursiveIteratorIterator( 385 | new RecursiveDirectoryIterator($spoolFolder), 386 | RecursiveIteratorIterator::LEAVES_ONLY, 387 | FilesystemIterator::SKIP_DOTS 388 | ); 389 | 390 | /** @var array $files */ 391 | $files = []; 392 | 393 | /** 394 | * @var string $name 395 | * @var SplFileObject $object 396 | */ 397 | foreach ($objects as $path => $object) { 398 | /** @noinspection TypeUnsafeComparisonInspection */ 399 | /** @noinspection NotOptimalIfConditionsInspection */ 400 | if ($object->getFilename() != '.' && $object->getFilename() != '..') { 401 | $filePath = str_replace($spoolFolder, '', $path); 402 | $file = [ 403 | 'name' => $object->getFilename(), 404 | 'path' => $path 405 | ]; 406 | $files[$filePath] = $file; 407 | } 408 | } 409 | 410 | return $files; 411 | } 412 | 413 | /** 414 | * Save all attachments of a given mail 415 | * 416 | * @param \Shockwavemk\Mail\Base\Model\Mail $mail 417 | * @return $this 418 | * @throws \Magento\Framework\Exception\FileSystemException 419 | * @throws \Magento\Framework\Exception\MailException 420 | * @throws \Exception 421 | */ 422 | public function saveAttachments($mail) 423 | { 424 | /** @var \Shockwavemk\Mail\Base\Model\Mail\Attachment[] $attachments */ 425 | $attachments = $mail->getAttachments(); 426 | 427 | foreach ($attachments as $attachment) { 428 | $this->saveAttachment($attachment); 429 | } 430 | 431 | return $this; 432 | } 433 | 434 | /** 435 | * Save an attachment binary to a file in host temp folder 436 | * 437 | * @param \Shockwavemk\Mail\Base\Model\Mail\Attachment $attachment 438 | * @return int $id 439 | * @throws \Magento\Framework\Exception\FileSystemException 440 | * @throws \Magento\Framework\Exception\MailException 441 | * @throws \Exception 442 | */ 443 | public function saveAttachment($attachment) 444 | { 445 | $binaryData = $attachment->getBinary(); 446 | /** @var \Shockwavemk\Mail\Base\Model\Mail $mail */ 447 | $mail = $attachment->getMail(); 448 | 449 | $folderPath = $this->getMailFolderPathById($mail->getId()) . 450 | DIRECTORY_SEPARATOR . 451 | self::ATTACHMENT_PATH; 452 | 453 | // create a folder for message if needed 454 | $this->createFolder($folderPath); 455 | 456 | // try to store message to filesystem 457 | $filePath = $this->storeFile( 458 | $binaryData, 459 | $this->getMailFolderPathById($mail->getId()) . 460 | DIRECTORY_SEPARATOR . 461 | self::ATTACHMENT_PATH . 462 | DIRECTORY_SEPARATOR . 463 | basename($attachment->getFilePath()) 464 | ); 465 | 466 | $attachment->setFilePath($filePath); 467 | } 468 | 469 | /** 470 | * Get Folder list for host spooler folder path 471 | * 472 | * @return string[] 473 | */ 474 | public function getRootLocalFolderFileList() 475 | { 476 | $spoolFolder = $this->_config->getHostSpoolerFolderPath(); 477 | 478 | /** @var IteratorIterator $objects */ 479 | $objects = new IteratorIterator( 480 | new DirectoryIterator($spoolFolder) 481 | ); 482 | 483 | /** @var array $files */ 484 | $files = []; 485 | 486 | /** 487 | * @var string $name 488 | * @var SplFileObject $object 489 | */ 490 | foreach ($objects as $path => $object) { 491 | /** @noinspection TypeUnsafeComparisonInspection */ 492 | /** @noinspection NotOptimalIfConditionsInspection */ 493 | if ($object->getFilename() != '.' && $object->getFilename() != '..') { 494 | /** @noinspection PhpMethodOrClassCallIsNotCaseSensitiveInspection */ 495 | $shortFilePath = str_replace($spoolFolder, '', $object->getPathName()); 496 | 497 | /** @noinspection PhpMethodOrClassCallIsNotCaseSensitiveInspection */ 498 | $files[] = [ 499 | 'name' => $object->getFilename(), 500 | 'localPath' => $object->getPathName(), 501 | 'remotePath' => $shortFilePath, 502 | 'modified' => $object->getMTime() 503 | ]; 504 | } 505 | } 506 | 507 | return $files; 508 | } 509 | 510 | /** 511 | * Get list of files in a local file path 512 | * 513 | * @param $localPath 514 | * @return string[] 515 | */ 516 | public function getLocalFileListForPath($localPath) 517 | { 518 | $files = []; 519 | $objects = new RecursiveIteratorIterator( 520 | new RecursiveDirectoryIterator($localPath), 521 | RecursiveIteratorIterator::LEAVES_ONLY, 522 | FilesystemIterator::SKIP_DOTS 523 | ); 524 | 525 | /** 526 | * @var string $path 527 | * @var SplFileObject $object 528 | */ 529 | foreach ($objects as $path => $object) { 530 | /** @noinspection TypeUnsafeComparisonInspection */ 531 | /** @noinspection NotOptimalIfConditionsInspection */ 532 | if ($object->getFilename() != '.' && $object->getFilename() != '..') { 533 | $hostTempFolderPath = $this->_config->getHostSpoolerFolderPath(); 534 | 535 | /** @noinspection PhpMethodOrClassCallIsNotCaseSensitiveInspection */ 536 | $remoteFilePath = str_replace($hostTempFolderPath, '/', $object->getPathName()); 537 | 538 | 539 | /** @noinspection PhpMethodOrClassCallIsNotCaseSensitiveInspection */ 540 | $file = [ 541 | 'name' => $object->getFilename(), 542 | 'localPath' => $object->getPathName(), 543 | 'remotePath' => $remoteFilePath, 544 | 'modified' => $object->getMTime() 545 | ]; 546 | 547 | $files[$object->getFilename()] = $file; 548 | } 549 | } 550 | 551 | return $files; 552 | } 553 | 554 | /** 555 | * Deletes all local stored files 556 | * 557 | * @param $localPath 558 | * @return \Shockwavemk\Mail\Base\Model\Storages\DefaultStorage 559 | */ 560 | public function deleteLocalFiles($localPath) 561 | { 562 | $it = new RecursiveDirectoryIterator( 563 | $localPath, 564 | RecursiveDirectoryIterator::SKIP_DOTS 565 | ); 566 | 567 | $files = new RecursiveIteratorIterator( 568 | $it, 569 | RecursiveIteratorIterator::CHILD_FIRST 570 | ); 571 | 572 | foreach ($files as $file) { 573 | if ($file->isDir()) { 574 | rmdir($file->getRealPath()); 575 | } else { 576 | unlink($file->getRealPath()); 577 | } 578 | } 579 | 580 | rmdir($localPath); 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /Model/Storages/StorageInterface.php: -------------------------------------------------------------------------------- 1 | areaList = $areaList; 56 | } 57 | 58 | /** 59 | * Get mail transport 60 | * 61 | * @return \Shockwavemk\Mail\Base\Model\Transports\TransportInterface 62 | */ 63 | public function getTransport() 64 | { 65 | $this->prepareMessage(); 66 | 67 | return $this->createTransport(); 68 | } 69 | 70 | /** 71 | * Prepare message 72 | * 73 | * @return $this 74 | * @throws \Zend_Mail_Exception 75 | */ 76 | protected function prepareMessage() 77 | { 78 | $template = $this->getTemplate(); 79 | 80 | $types = [ 81 | TemplateTypesInterface::TYPE_TEXT => MessageInterface::TYPE_TEXT, 82 | TemplateTypesInterface::TYPE_HTML => MessageInterface::TYPE_HTML, 83 | ]; 84 | 85 | // Bugfix for not translated cron scheduled mails 86 | $areaObject = $this->areaList->getArea($template->getDesignConfig()->getArea()); 87 | $areaObject->load(\Magento\Framework\App\Area::PART_TRANSLATE); 88 | 89 | $body = $template->processTemplate(); 90 | $this->message->setMessageType($types[$template->getType()]) 91 | ->setBody($body) 92 | ->setSubject($template->getSubject()); 93 | 94 | return $this; 95 | } 96 | 97 | /** 98 | * @return \Shockwavemk\Mail\Base\Model\Transports\TransportInterface 99 | */ 100 | public function createTransport() 101 | { 102 | // Verify to get a clean mail instance 103 | $this->setMail(null); 104 | 105 | // Transfer TransportData to Mail 106 | $this->updateMailWithTransportData(); 107 | 108 | /** @var \Shockwavemk\Mail\Base\Model\Transports\TransportInterface $mailTransport */ 109 | $mailTransport = $this->mailTransportFactory 110 | ->create( 111 | ['message' => clone $this->message] 112 | ); 113 | 114 | $mailTransport->setMail($this->getMail()); 115 | 116 | // Cleanup of mail and message model used in this transport 117 | $this->reset(); 118 | 119 | return $mailTransport; 120 | } 121 | 122 | /** 123 | * @return \Shockwavemk\Mail\Base\Model\Mail 124 | */ 125 | public function updateMailWithTransportData() 126 | { 127 | $this->getMail() 128 | ->setSubject($this->message->getSubject()) 129 | ->setTemplateIdentifier($this->templateIdentifier) 130 | ->setTemplateModel($this->templateModel) 131 | ->setVars($this->templateVars) 132 | ->setOptions($this->templateOptions) 133 | ->setCustomerId($this->getCustomerId()) 134 | ->setStoreId($this->getStoreId()) 135 | ->setLanguageCode($this->getLanguageCode()) 136 | ->setSenderMail($this->getSenderMail()); 137 | 138 | return $this; 139 | } 140 | 141 | /** 142 | * Get mail 143 | * 144 | * @return \Shockwavemk\Mail\Base\Model\Mail 145 | * @throws \Magento\Framework\Exception\MailException 146 | */ 147 | public function getMail() 148 | { 149 | /** @noinspection IsEmptyFunctionUsageInspection */ 150 | if (empty($this->_mail)) { 151 | $this->_mail = $this->objectManager 152 | ->create('Shockwavemk\Mail\Base\Model\Mail'); 153 | 154 | /** @var \Shockwavemk\Mail\Base\Model\Mail\AttachmentCollection $attachmentCollection */ 155 | $attachmentCollection = $this->objectManager 156 | ->get('Shockwavemk\Mail\Base\Model\Mail\AttachmentCollection'); 157 | 158 | foreach ($attachmentCollection as $attachment) { 159 | $this->_mail->addAttachment($attachment); 160 | } 161 | } 162 | 163 | return $this->_mail; 164 | } 165 | 166 | /** 167 | * @param $mail \Shockwavemk\Mail\Base\Model\Mail 168 | * @return $this \Shockwavemk\Mail\Base\Model\Template\TransportBuilder 169 | */ 170 | public function setMail($mail) 171 | { 172 | $this->_mail = $mail; 173 | return $this; 174 | } 175 | 176 | /** 177 | * @return int|null 178 | */ 179 | public function getCustomerId() 180 | { 181 | if (empty($this->_customerId)) { 182 | 183 | if (!empty($this->templateVars['customer'])) { 184 | /** @var \Magento\Customer\Model\Customer $customer */ 185 | $customer = $this->templateVars['customer']; 186 | return $customer->getId(); 187 | } 188 | 189 | if (!empty($this->templateVars['order'])) { 190 | /** @var \Magento\Sales\Model\Order $order */ 191 | $order = $this->templateVars['order']; 192 | return $order->getCustomerId(); 193 | } 194 | 195 | if (!empty($this->templateVars['rma'])) { 196 | /** @var \Magento\Rma\Model\Rma $rma */ 197 | $rma = $this->templateVars['rma']; 198 | return !empty($rma->getCustomerId()) ? $rma->getCustomerId() : $rma->getOrder()->getCustomerId(); 199 | } 200 | } 201 | 202 | return $this->_customerId; 203 | } 204 | 205 | /** 206 | * @return int|null 207 | */ 208 | public function getStoreId() 209 | { 210 | if (empty($this->_storeId)) { 211 | 212 | if (!empty($this->templateVars['store'])) { 213 | /** @var \Magento\Store\Model\Store $store */ 214 | $store = $this->templateVars['store']; 215 | return $store->getId(); 216 | } 217 | } 218 | 219 | return $this->_storeId; 220 | } 221 | 222 | /** 223 | * @return null|string 224 | */ 225 | public function getLanguageCode() 226 | { 227 | if (empty($this->_languageCode)) { 228 | 229 | /** @var \Magento\Framework\Locale\Resolver $resolver */ 230 | $resolver = $this->objectManager->get('Magento\Framework\Locale\Resolver'); 231 | $resolver->emulate($this->getStoreId()); 232 | 233 | return $resolver->getLocale(); 234 | } 235 | 236 | return $this->_languageCode; 237 | } 238 | 239 | /** 240 | * @return mixed 241 | */ 242 | public function getSenderMail() 243 | { 244 | return $this->_sender; 245 | } 246 | 247 | /** 248 | * Get mail transport with a backup of a existing messageString 249 | * 250 | * @param \Shockwavemk\Mail\Base\Model\Mail\MessageInterface $message 251 | * @return \Shockwavemk\Mail\Base\Model\Transports\TransportInterface 252 | */ 253 | public function getBackupTransport($message) 254 | { 255 | $this->prepareMessage(); 256 | 257 | // replace message 258 | $this->message = $message; 259 | 260 | return $this->createTransport(); 261 | } 262 | 263 | /** 264 | * Set mail from address 265 | * 266 | * @param string|array $from 267 | * @return $this 268 | * @throws \Magento\Framework\Exception\MailException 269 | */ 270 | public function setFrom($from) 271 | { 272 | $result = $this->_senderResolver->resolve($from, $this->getStoreId()); 273 | $this->setSenderMail($result); 274 | $this->message->setFrom($result['email'], $result['name']); 275 | 276 | return $this; 277 | } 278 | 279 | /** 280 | * @param mixed $sender 281 | */ 282 | public function setSenderMail($sender) 283 | { 284 | $this->_sender = $sender; 285 | } 286 | 287 | /** 288 | * @return array 289 | */ 290 | public function getTemplateOptions() 291 | { 292 | return $this->templateOptions; 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /Model/Transports/Base.php: -------------------------------------------------------------------------------- 1 | _message = $message; 47 | $transportClassName = $config->getTransportClassName(); 48 | $this->_transport = $manager->create( 49 | $transportClassName, 50 | ['message' => $message] 51 | ); 52 | 53 | } catch (\Exception $e) { 54 | throw new MailException( 55 | new Phrase($e->getMessage()), 56 | $e); 57 | } 58 | } 59 | 60 | /** 61 | * Send a mail using this transport 62 | * 63 | * @return Base 64 | * @throws MailException 65 | */ 66 | public function sendMessage() 67 | { 68 | try { 69 | // First: Send message with given transport 70 | $this->_transport->sendMessage(); 71 | 72 | // Second: Create a mail instance to store 73 | $mail = $this->getMail(); 74 | $mail->updateWithTransport($this->_transport); 75 | $mail->save(); 76 | 77 | } catch (\Exception $e) { 78 | throw new MailException( 79 | new Phrase($e->getMessage()), 80 | $e 81 | ); 82 | } 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Get the mail object of this transport 89 | * 90 | * @return Mail 91 | */ 92 | public function getMail() 93 | { 94 | return $this->_transport->getMail(); 95 | } 96 | 97 | /** 98 | * Associate a mail object with this transport 99 | * Mail object is used to store transport information and sent message 100 | * 101 | * @param Mail $mail 102 | * @return TransportInterface 103 | */ 104 | public function setMail($mail) 105 | { 106 | $this->_transport->setMail($mail); 107 | return $this; 108 | } 109 | 110 | /** 111 | * Get message associated with this transport 112 | * 113 | * @return \Shockwavemk\Mail\Base\Model\Mail\MessageInterface 114 | */ 115 | public function getMessage() 116 | { 117 | return $this->_transport->getMessage(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Model/Transports/TransportInterface.php: -------------------------------------------------------------------------------- 1 | objectManager = $objectManager; 170 | $this->documentConfig = $documentConfig; 171 | 172 | // Dynamic typed build a collection for attachments for later usage 173 | 174 | // Should be extended from \Magento\Framework\Data\Collection 175 | $this->attachmentCollection = $this->objectManager->get( 176 | 'Shockwavemk\Mail\Base\Model\Mail\AttachmentCollectionInterface' 177 | ); 178 | } 179 | 180 | 181 | 182 | /** 183 | * Handles asynchronous email sending 184 | * 185 | * @return void 186 | * @throws \RuntimeException 187 | * @throws \Exception 188 | */ 189 | public function sendEmails() 190 | { 191 | /** @var \Magento\Sales\Model\AbstractModel $item */ 192 | foreach ($this->entityCollection->getItems() as $item) { 193 | 194 | // add this code 195 | 196 | // Create a new attachment 197 | 198 | if (!is_null($attachmentFilePath)) { 199 | /** @var \Shockwavemk\Mail\Base\Model\Mail\AttachmentInterface|\Magento\Framework\DataObject $attachment */ 200 | $attachment = $this->objectManager 201 | ->create('Shockwavemk\Mail\Base\Model\Mail\AttachmentInterface'); 202 | 203 | // Do not transfer binary data to mail entity at this point: The mailer can handle file reading on its own 204 | $attachment->setFilePath($attachmentFilePath); 205 | 206 | // Add attachment to attachment collection 207 | // Let the mailer later decide how to handle them 208 | $this->attachmentCollection->addItem($attachment); 209 | } 210 | 211 | } 212 | 213 | } 214 | 215 | ``` 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /Setup/InstallSchema.php: -------------------------------------------------------------------------------- 1 | startSetup(); 26 | 27 | $installer->endSetup(); 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Setup/UpgradeSchema.php: -------------------------------------------------------------------------------- 1 | startSetup(); 14 | if (version_compare($context->getVersion(), '1.0.1') < 0) { 15 | $this->createMailMainTable($setup); 16 | } 17 | 18 | $setup->endSetup(); 19 | } 20 | 21 | /** 22 | * @param $installer 23 | */ 24 | protected function createMailMainTable(SetupInterface $installer) 25 | { 26 | $table = $installer->getConnection()->newTable( 27 | $installer->getTable('shockwavemk_mail_entity') 28 | )->addColumn( 29 | 'entity_id', 30 | \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, 31 | null, 32 | ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], 33 | 'Mail ID' 34 | )->addColumn( 35 | 'customer_id', 36 | \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, 37 | null, 38 | ['unsigned' => true, 'nullable' => false, 'default' => '0'], 39 | 'Customer ID' 40 | )->addColumn( 41 | 'template_identifier', 42 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 43 | 255, 44 | ['nullable' => false], 45 | 'Template Identifier' 46 | )->addColumn( 47 | 'subject', 48 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 49 | 255, 50 | ['nullable' => false], 51 | 'Subject' 52 | )->addColumn( 53 | 'template_model', 54 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 55 | 255, 56 | ['nullable' => false], 57 | 'Template Model' 58 | )->addColumn( 59 | 'store_id', 60 | \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, 61 | null, 62 | ['unsigned' => true, 'nullable' => false, 'default' => '0'], 63 | 'Store ID' 64 | )->addColumn( 65 | 'sent', 66 | \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, 67 | null, 68 | ['unsigned' => true, 'nullable' => false, 'default' => '0'], 69 | 'Successful sent (0 or 1)' 70 | )->addColumn( 71 | 'sent_at', 72 | \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, 73 | null, 74 | [], 75 | 'Sent at date' 76 | )->addColumn( 77 | 'recipients', 78 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 79 | \Magento\Framework\DB\Ddl\Table::MAX_TEXT_SIZE, 80 | ['nullable' => false], 81 | 'Recipients' 82 | )->addColumn( 83 | 'language_code', 84 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 85 | 255, 86 | ['nullable' => false], 87 | 'Language Code' 88 | )->addColumn( 89 | 'sender_mail', 90 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 91 | 255, 92 | ['nullable' => false], 93 | 'Sender Mail' 94 | )->addColumn( 95 | 'vars', 96 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 97 | \Magento\Framework\DB\Ddl\Table::MAX_TEXT_SIZE, 98 | ['nullable' => false], 99 | 'Vars' 100 | )->addColumn( 101 | 'options', 102 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 103 | \Magento\Framework\DB\Ddl\Table::MAX_TEXT_SIZE, 104 | ['nullable' => false], 105 | 'Options' 106 | )->addColumn( 107 | 'recipient_variables', 108 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 109 | \Magento\Framework\DB\Ddl\Table::MAX_TEXT_SIZE, 110 | ['nullable' => false], 111 | 'Recipient variables' 112 | )->addColumn( 113 | 'delivery_time', 114 | \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, 115 | null, 116 | [], 117 | 'Delivery time' 118 | )->addColumn( 119 | 'tags', 120 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 121 | \Magento\Framework\DB\Ddl\Table::MAX_TEXT_SIZE, 122 | ['nullable' => false], 123 | 'Tags' 124 | )->addColumn( 125 | 'test_mode', 126 | \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, 127 | null, 128 | ['unsigned' => true, 'nullable' => false, 'default' => '0'], 129 | 'Test mode' 130 | )->addColumn( 131 | 'tracking_enabled', 132 | \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, 133 | null, 134 | ['unsigned' => true, 'nullable' => false, 'default' => '0'], 135 | 'Tracking enabled' 136 | )->addColumn( 137 | 'tracking_clicks_enabled', 138 | \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, 139 | null, 140 | ['unsigned' => true, 'nullable' => false, 'default' => '0'], 141 | 'Tracking Clicks Enabled' 142 | )->addColumn( 143 | 'tracking_opens_enabled', 144 | \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, 145 | null, 146 | ['unsigned' => true, 'nullable' => false, 'default' => '0'], 147 | 'Tracking Opens Enabled' 148 | )->addColumn( 149 | 'transport_id', 150 | \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 151 | 255, 152 | ['nullable' => true], 153 | 'Transport Id' 154 | )->addIndex( 155 | $installer->getIdxName('mail', 'sent'), 156 | 'sent' 157 | )->addIndex( 158 | $installer->getIdxName('mail', 'sent_at'), 159 | 'sent_at' 160 | )->addIndex( 161 | $installer->getIdxName('mail', 'customer_id'), 162 | 'customer_id' 163 | )->setComment( 164 | 'Mail main table' 165 | ); 166 | $installer->getConnection()->createTable($table); 167 | } 168 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shockwavemk/magento2-module-mail", 3 | "description": "Base module for extending Magento2 mail features", 4 | "require": { 5 | }, 6 | "type": "magento2-module", 7 | "license": [ 8 | "Apache-2.0" 9 | ], 10 | "autoload": { 11 | "files": [ 12 | "registration.php" 13 | ], 14 | "psr-4": { 15 | "Shockwavemk\\Mail\\Base\\": "" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /docs/enhanced-magento2-customer-detail-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockwavemk/magento2-module-mail/22929cfe0fd78770783487994cd53333a41c3d4b/docs/enhanced-magento2-customer-detail-menu.png -------------------------------------------------------------------------------- /docs/local-storeage-for-mails-as-json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockwavemk/magento2-module-mail/22929cfe0fd78770783487994cd53333a41c3d4b/docs/local-storeage-for-mails-as-json.png -------------------------------------------------------------------------------- /docs/magento2-config-mail-enhancement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockwavemk/magento2-module-mail/22929cfe0fd78770783487994cd53333a41c3d4b/docs/magento2-config-mail-enhancement.png -------------------------------------------------------------------------------- /docs/magento2-customer-mail-transactional-review-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockwavemk/magento2-module-mail/22929cfe0fd78770783487994cd53333a41c3d4b/docs/magento2-customer-mail-transactional-review-detail.png -------------------------------------------------------------------------------- /docs/magento2-customer-mail-transactional-review-with-mail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockwavemk/magento2-module-mail/22929cfe0fd78770783487994cd53333a41c3d4b/docs/magento2-customer-mail-transactional-review-with-mail.png -------------------------------------------------------------------------------- /docs/magento2-customer-mail-transactional-review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockwavemk/magento2-module-mail/22929cfe0fd78770783487994cd53333a41c3d4b/docs/magento2-customer-mail-transactional-review.png -------------------------------------------------------------------------------- /docs/magento2-customer-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockwavemk/magento2-module-mail/22929cfe0fd78770783487994cd53333a41c3d4b/docs/magento2-customer-menu.png -------------------------------------------------------------------------------- /docs/magento2-guest-email-review-and-resending.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockwavemk/magento2-module-mail/22929cfe0fd78770783487994cd53333a41c3d4b/docs/magento2-guest-email-review-and-resending.jpg -------------------------------------------------------------------------------- /docs/magento2-mail-resending-and-recalculation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockwavemk/magento2-module-mail/22929cfe0fd78770783487994cd53333a41c3d4b/docs/magento2-mail-resending-and-recalculation.png -------------------------------------------------------------------------------- /docs/magento2-mail-stored-as-json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockwavemk/magento2-module-mail/22929cfe0fd78770783487994cd53333a41c3d4b/docs/magento2-mail-stored-as-json.png -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /etc/adminhtml/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /etc/adminhtml/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | Shockwavemk\Mail\Base\Model\Config\Source\Type 15 | 16 | 17 | 18 | Shockwavemk\Mail\Base\Model\Config\Source\Storage 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Magento\Config\Model\Config\Source\Yesno 37 | 38 | 39 | 40 | 41 | Magento\Config\Model\Config\Source\Yesno 42 | 43 | 44 | 45 | 46 | Magento\Config\Model\Config\Source\Yesno 47 | 48 | 49 | 50 | 51 | Magento\Config\Model\Config\Source\Yesno 52 | 53 | 54 | 55 |
56 |
57 |
58 | 59 | -------------------------------------------------------------------------------- /etc/cache.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | default 13 | default 14 | 100 15 | 3 16 | /var/www/html/pub/media/emails 17 | 5 18 | 19 | 20 | 21 | 22 | 23 | 24 | default 25 | \Magento\Framework\Mail\Transport 26 | 27 | 28 | 29 | 30 | 31 | default 32 | \Shockwavemk\Mail\Base\Model\Storages\DefaultStorage 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /i18n/en_US.csv: -------------------------------------------------------------------------------- 1 | TODO,TODO -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /view/adminhtml/layout/customer_index_mail.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | Shockwavemk\Mail\Base\Model\ResourceModel\Mail\Collection 13 | mailGrid 14 | true 15 | added_at 16 | DESC 17 | 1 18 | 19 | 1 20 | 21 | 22 | 23 | 24 | No Items Found 25 | 26 | 27 | 28 | Subject 29 | subject 30 | text 31 | subject 32 | Shockwavemk\Mail\Base\Block\Adminhtml\Widget\Grid\Column\Filter\Text 33 | col-name 34 | col-name 35 | 36 | 37 | 38 | 39 | Template 40 | template_identifier 41 | text 42 | template_identifier 43 | Shockwavemk\Mail\Base\Block\Adminhtml\Edit\Tab\Mail\Grid\Renderer\Template 44 | Shockwavemk\Mail\Base\Block\Adminhtml\Widget\Grid\Column\Filter\Text 45 | 46 | 47 | 48 | 49 | Store 50 | store_id 51 | store 52 | 53 | 54 | 55 | 56 | Sent at 57 | sent_at 58 | 1 59 | datetime 60 | sent_at 61 | 62 | 63 | 64 | 65 | Sent 66 | sent 67 | number 68 | sent 69 | Shockwavemk\Mail\Base\Block\Adminhtml\Edit\Tab\Mail\Grid\Renderer\Sent 70 | Shockwavemk\Mail\Base\Block\Adminhtml\Widget\Grid\Column\Filter\Text 71 | 72 | 73 | 74 | 75 | Recipients 76 | recipients 77 | longtext 78 | recipients 79 | Shockwavemk\Mail\Base\Block\Adminhtml\Edit\Tab\Mail\Grid\Renderer\Recipients 80 | Shockwavemk\Mail\Base\Block\Adminhtml\Widget\Grid\Column\Filter\Text 81 | 82 | 83 | 84 | 85 | Language 86 | language_code 87 | text 88 | language_code 89 | 90 | 91 | 92 | 93 | Actions 94 | action 95 | text 96 | entity_id 97 | Shockwavemk\Mail\Base\Block\Adminhtml\Edit\Tab\Mail\Grid\Renderer\Action 98 | Shockwavemk\Mail\Base\Block\Adminhtml\Widget\Grid\Column\Filter\Text 99 | 100 | 101 | 102 | 103 | 104 | mailGridJsObject 105 | Shockwavemk_Mail_Base::customer/edit/tab/mail.phtml 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /view/adminhtml/layout/customer_mail_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /view/adminhtml/layout/customer_mail_overview.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /view/adminhtml/layout/customer_mail_send.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /view/adminhtml/layout/overview_block.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | Shockwavemk\Mail\Base\Model\ResourceModel\Mail\Collection 14 | mailGrid 15 | added_at 16 | DESC 17 | 1 18 | 19 | 20 | 21 | No Items Found 22 | 23 | 24 | 25 | Customer 26 | customer_id 27 | text 28 | customer_id 29 | Shockwavemk\Mail\Base\Block\Adminhtml\Edit\Tab\Mail\Grid\Renderer\Customer 30 | Shockwavemk\Mail\Base\Block\Adminhtml\Widget\Grid\Column\Filter\Text 31 | 32 | 33 | 34 | 35 | Subject 36 | subject 37 | text 38 | subject 39 | Shockwavemk\Mail\Base\Block\Adminhtml\Widget\Grid\Column\Filter\Text 40 | col-name 41 | col-name 42 | 43 | 44 | 45 | 46 | Template 47 | template_identifier 48 | text 49 | template_identifier 50 | Shockwavemk\Mail\Base\Block\Adminhtml\Edit\Tab\Mail\Grid\Renderer\Template 51 | Shockwavemk\Mail\Base\Block\Adminhtml\Widget\Grid\Column\Filter\Text 52 | 53 | 54 | 55 | 56 | Store 57 | store_id 58 | store 59 | 60 | 61 | 62 | 63 | Sent at 64 | sent_at 65 | 1 66 | datetime 67 | sent_at 68 | 69 | 70 | 71 | 72 | Sent 73 | sent 74 | number 75 | sent 76 | Shockwavemk\Mail\Base\Block\Adminhtml\Edit\Tab\Mail\Grid\Renderer\Sent 77 | Shockwavemk\Mail\Base\Block\Adminhtml\Widget\Grid\Column\Filter\Text 78 | 79 | 80 | 81 | 82 | Recipients 83 | recipients 84 | longtext 85 | recipients 86 | Shockwavemk\Mail\Base\Block\Adminhtml\Edit\Tab\Mail\Grid\Renderer\Recipients 87 | Shockwavemk\Mail\Base\Block\Adminhtml\Widget\Grid\Column\Filter\Text 88 | 89 | 90 | 91 | 92 | Language 93 | language_code 94 | text 95 | language_code 96 | 97 | 98 | 99 | 100 | Actions 101 | action 102 | text 103 | entity_id 104 | Shockwavemk\Mail\Base\Block\Adminhtml\Edit\Tab\Mail\Grid\Renderer\Action 105 | Shockwavemk\Mail\Base\Block\Adminhtml\Widget\Grid\Column\Filter\Text 106 | 107 | 108 | 109 | 110 | 111 | mailGridJsObject 112 | Shockwavemk_Mail_Base::customer/edit/tab/mail.phtml 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /view/adminhtml/templates/customer/edit/tab/mail.phtml: -------------------------------------------------------------------------------- 1 | 13 | 52 | -------------------------------------------------------------------------------- /view/adminhtml/templates/customer/mail/edit/info.phtml: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 |
5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | getMail()->getOrderId())): ?> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | getMail()->getCustomerId())): ?> 29 | 30 | 31 | 32 | 33 | getUrl('customer/index/edit', array('id' => $this->getMail()->getCustomerId())); ?> 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | getMail()->getSentAt())): ?> 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | getMail()->getDeliveryTime())): ?> 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | getMail()->getRecipients())): ?> 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | getMail()->getSenderMail())): ?> 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | getMail()->getTransportId())): ?> 170 | 171 | 172 | 173 | getMail()->getTransportId()) ?> 174 | 175 | 176 | 177 | 178 | 179 | 180 | getMail()->getAttachments())): ?> 181 | 182 | 183 | 184 | getMail()->getAttachments() as $attachment): 185 | /** @var \Shockwavemk\Mail\Base\Model\Mail\Attachment $attachment */ 186 | ?> 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 |
getMail()->getId() ?>
 
getMail()->getOrderId() ?>
 
getMail()->getCustomerId() ?>
 
htmlEscape($this->getMail()->getTemplateIdentifier()) ?>
 
getMail()->getSentAt() ?>
 
getMail()->getDeliveryTime() ?>
 
getMail()->getSent() ? 'Yes' : 'No') ?>
 
htmlEscape(implode(', ', $this->getMail()->getRecipients())) ?>htmlEscape($this->getMail()->getRecipients()) ?>
 
htmlEscape(implode(', ', $this->getMail()->getTags())) ?>
 
htmlEscape($this->getStoreName()) ?>
 
getMail()->getLanguageCode() ?>
 
htmlEscape(implode(', ', $this->getMail()->getSenderMail())) ?>htmlEscape($this->getMail()->getSenderMail()) ?>
 
getMail()->getTestMode() ? 'Yes' : 'No' ?>
 
getMail()->getTrackingEnabled() ? 'Yes' : 'No' ?>
 
getMail()->getTrackingClicksEnabled() ? 'Yes' : 'No' ?>
 
getMail()->getTrackingOpensEnabled() ? 'Yes' : 'No' ?>
 
...
 
getFileName() ?>
 
197 |
198 | -------------------------------------------------------------------------------- /view/adminhtml/templates/customer/mail/edit/view.phtml: -------------------------------------------------------------------------------- 1 |
2 | getMail()->getMessage()->getBodyHtml(); 7 | 8 | if(!empty($bodyHtml)) { 9 | $html = $bodyHtml->getContent(); 10 | $html = quoted_printable_decode($html); 11 | } 12 | 13 | ?> 14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /view/adminhtml/templates/customer/mail/send/form.phtml: -------------------------------------------------------------------------------- 1 | 11 | --------------------------------------------------------------------------------