-
Notifications
You must be signed in to change notification settings - Fork 9.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add events to transactional email sending #42
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php | ||
|
||
class Mage_Core_Helper_Mail extends Mage_Core_Helper_Abstract | ||
{ | ||
/** | ||
* Return a mailer instance to be used by the Mage_Core_Model_Email_Template | ||
* | ||
* The used factory helper and method can be configured at global/email/factory_helper. | ||
* Default is Mage_Core_Helper_Mail::getMailer. | ||
* | ||
* @return Zend_Mail | ||
*/ | ||
public function getMailer() | ||
{ | ||
return new Zend_Mail('utf-8'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,6 +73,7 @@ class Mage_Core_Model_Email_Template extends Mage_Core_Model_Template | |
* Configuration path for default email templates | ||
*/ | ||
const XML_PATH_TEMPLATE_EMAIL = 'global/template/email'; | ||
const XML_PATH_MAIL_FACTORY = 'global/email/factory_helper'; | ||
const XML_PATH_SENDING_SET_RETURN_PATH = 'system/smtp/set_return_path'; | ||
const XML_PATH_SENDING_RETURN_PATH_EMAIL = 'system/smtp/return_path_email'; | ||
const XML_PATH_DESIGN_EMAIL_LOGO = 'design/email/logo'; | ||
|
@@ -140,11 +141,20 @@ protected function _getLogoAlt($store) | |
/** | ||
* Retrieve mail object instance | ||
* | ||
* Configure the factory helper at global/email/factory_helper using the format class::method. | ||
* Default is Mage_Core_Helper_Mail::getMailer. | ||
* | ||
* @return Zend_Mail | ||
*/ | ||
protected function _getMail() | ||
{ | ||
return new Zend_Mail('utf-8'); | ||
$factory = Mage::getConfig()->getNode(self::XML_PATH_MAIL_FACTORY); | ||
$parts = explode('::', (string) $factory); | ||
if (count($parts) != 2) { | ||
throw new Exception(sprintf('Failed to get mail factory class and method.')); | ||
} | ||
$mailer = call_user_func(array(Mage::helper($parts[0]), $parts[1])); | ||
return $mailer; | ||
} | ||
|
||
/** | ||
|
@@ -349,25 +359,31 @@ public function getProcessedTemplate(array $variables = array()) | |
$variables['this'] = $this; | ||
} | ||
|
||
if (isset($variables['subscriber']) && ($variables['subscriber'] instanceof Mage_Newsletter_Model_Subscriber)) { | ||
$processor->setStoreId($variables['subscriber']->getStoreId()); | ||
} | ||
|
||
if (!isset($variables['logo_url'])) { | ||
$variables['logo_url'] = $this->_getLogoUrl($processor->getStoreId()); | ||
} | ||
if (!isset($variables['logo_alt'])) { | ||
$variables['logo_alt'] = $this->_getLogoAlt($processor->getStoreId()); | ||
} | ||
|
||
$processor->setIncludeProcessor(array($this, 'getInclude')) | ||
->setVariables($variables); | ||
$processor->setIncludeProcessor(array($this, 'getInclude')); | ||
|
||
$this->_applyDesignConfig(); | ||
$storeId = $this->getDesignConfig()->getStore(); | ||
try { | ||
$processedResult = $processor->setStoreId($storeId) | ||
->filter($this->getPreparedTemplateText()); | ||
$processor->setStoreId($storeId); | ||
$transport = new Varien_Object(array( | ||
'variables' => new Varien_Object($variables), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have applied this change, but slightly refactored this part: instead of creating an object, I pass the original array and then reassign it after event is dispatched. And in the code below the magic methods of "transport" are not used, because it is knowingly UPD: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks again, Anton. I used a Varien_Object for the variables array because that enables the use of nice chained calls like
A brief explanation regarding the event placement: I added the event in this way, because this is how the setting of the store_id of the newsletter/subscriber currently works in Magento 1. |
||
'template_text' => $this->getPreparedTemplateText(), | ||
'store_id' => $storeId | ||
)); | ||
// Use observer to modify variables and template processor settings | ||
Mage::dispatchEvent('email_template_filter_before', array( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is an unwritten convention among core developers to have module name prefix in the event names. So I have changed this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the explanation, I think that convention makes a lot of sense. |
||
'processor' => $processor, | ||
'transport' => $transport | ||
)); | ||
if (!isset($variables['logo_url'])) { | ||
$variables['logo_url'] = $this->_getLogoUrl($transport->getStoreId()); | ||
} | ||
if (!isset($variables['logo_alt'])) { | ||
$variables['logo_alt'] = $this->_getLogoAlt($transport->getStoreId()); | ||
} | ||
|
||
$processedResult = $processor->setVariables($transport->getVariables()->getData()) | ||
->filter($transport->getTemplateText()); | ||
} catch (Exception $e) { | ||
$this->_cancelDesignConfig(); | ||
throw $e; | ||
|
@@ -486,6 +502,13 @@ public function send($email, $name = null, array $variables = array()) | |
$result = false; | ||
$this->_sendingException = null; | ||
try { | ||
// Note: the email body already has been processed, changes to variables will have no effect | ||
Mage::dispatchEvent('email_template_send_before', array( | ||
'mailer' => $mail, | ||
'variables' => new Varien_Object($variables), | ||
'template' => $this, | ||
'store_id' => $this->getDesignConfig()->getStore() | ||
)); | ||
$mail->send(); | ||
$result = true; | ||
} catch (Exception $e) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -89,7 +89,13 @@ | |
<page_type> | ||
<render_inherited>0</render_inherited> | ||
</page_type> | ||
<design_fallback> | ||
<allow_map_update>1</allow_map_update> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this change relevant to the contribution topic? I don't think so. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, this was a mistake, sorry. |
||
</design_fallback> | ||
</dev> | ||
<email> | ||
<factory_helper>Mage_Core_Helper_Mail::getMailer</factory_helper> | ||
</email> | ||
</global> | ||
<frontend> | ||
<routers> | ||
|
@@ -291,7 +297,7 @@ | |
</web> | ||
<admin> | ||
<startup> | ||
<page>dashboard</page> | ||
<menu_item_id>dashboard</menu_item_id> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also looks like an accidental change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, you are right. |
||
</startup> | ||
<url> | ||
<use_custom>0</use_custom> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,4 +69,23 @@ public function scheduledSend($schedule) | |
|
||
$collection->walk('sendPerSubscriber', array($countOfSubscritions)); | ||
} | ||
|
||
/** | ||
* Set the subscriber store id on the transport object | ||
* | ||
* Set the subscriber store id on the transport object so the logo configuration for the | ||
* subscriber store is read (which is not necessarily the current one). | ||
* | ||
* @param Varien_Object $observer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it will be the
Technically your solution would work though. If you inspect the
It is contradictory to the concept of event subscriber and potentially can lead to a logical error, if the The bottom line: use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for clarification. Personally I second deprecating the event instance wrapper for the event parameters :) |
||
* @return Mage_Newsletter_Model_Observer | ||
*/ | ||
public function emailTemplateFilterBefore($observer) | ||
{ | ||
// Only match newsletter subscriber events | ||
$subscriber = $observer->getTransport()->getVariables()->getSubscriber(); | ||
if ($subscriber && $subscriber instanceof Mage_Newsletter_Model_Subscriber) { | ||
$observer->getTransport()->setStoreId($subscriber->getStoreId()); | ||
} | ||
return $this; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to return a value here. Removed from the commit. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See previous comment. Most observers in Magento 2 return the observer instance, so I thought that might be the new recommended practice. Thanks again for all your detailed comments, I highly appreciate it! |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I remember, this method was added specifically for purpose of rewriting/mocking this class. Well, it turns out to be not the most flexible solution.
Instead of introducing more complexity to it, I'd recommend to look at it from different angle:
_mailer
attribute) with "lazy initialization" getter. And implement setter.send()
methodBoth approaches supposedly will give more flexibility and remove necessity to rewrite the class. Besides, such changes can be covered by a unit test (unlike the proposed solution which is too coupled to application instance, hence integration tests)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, Anton. I prefer your solution.
Now one thing missing is a hook to allow global mailer injection even if the first call to the (lazy loading) factory method is from within a core class.
Usually some very early event like
controller_front_init_before
be used for that, except that this one is only fired if the routing process is dispatched. cron scripts (for sending newsletters for example) would not be affected.Is there a method for mailer injection that affects all instances you would suggest? Maybe a new event is needed, like
core_email_get_instance_before
?