I have following action:
http://localhost/admin/catalog/product_attribute/edit/attribute_id/135/key/…/
I would like to do some extra things with attribute after saving.
I have created and registered custom plugin in Vendor/Module/Plugin/Model/ResourceModel/Attribute/Save.php with following content:
class Save
{
/**
* #var Config
*/
protected $config;
/**
* #param Config $config
*/
public function __construct(Config $config, TypeListInterface $typeList)
{
$this->config = $config;
}
/**
*
* #param Attribute $subject
* #param Attribute $result
* #return Attribute $result
*
*/
public function afterSave(Attribute $subject, Attribute $result)
{
# Do something
}
}
I have also added following entry to di.xml:
<type name="Magento\Catalog\Model\ResourceModel\Attribute">
<plugin name="do_stuff_after_attribute_save" type="Vendor\Module\Plugin\Model\ResourceModel\Attribute\Save" />
</type>
But the plugin seems not to work. Even if I die('somenthing'); or try to log to file, the code is not executed after saving the attribute.
Maybe I am trying to overwrite wrong method?
You could follow the points below:
You should use di.xml in the adminhtml folder as it is a backend issue.
You should override the execute method of this Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save controller class.
File: app/code/Milandev/Testplugin/etc/adminhtml/di.xml
<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save">
<plugin disabled="false" name="Milandev_Admin_Product_Attribute_Save" sortOrder="10" type="Milandev\Testplugin\Plugin\Catalog\Controller\Adminhtml\Product\Attribute\Save"/>
</type>
</config>
File: app/code/Milandev/Testplugin/Plugin/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
<?php
namespace Milandev\Testplugin\Plugin\Catalog\Controller\Adminhtml\Product\Attribute;
class Save
{
public function afterExecute(
\Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save $subject,
$result
) {
die('hello world!');
//Your plugin code
}
}
I ran into the same issue some time ago. Turned out that there was another plugin installed which tried to handle the exact same class and property. After adding the sortOrder attribute on both di.xml files, with of course a different value for both, everything worked fine.
Try to make sure that your plugin is applied and all other plugins return expected values.
Go to generated/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute/Interceptor.php (if it's already generated). If not, just enable developer mode (it will be generated automatically) or run php bin/magento setup:di:compile for production mode.
Look for afterSave() method and print the list of available plugins. E.g.
/**
* {#inheritdoc}
*/
public function afterSave()
{
$pluginInfo = $this->pluginList->getNext($this->subjectType, 'afterSave');
echo "<pre>";
print_r($pluginInfo);
die;
if (!$pluginInfo) {
return parent::afterSave();
} else {
return $this->___callPlugins('afterSave', func_get_args(), $pluginInfo);
}
}
Then you should see the list of enabled plugins (in your case it might be save_swatches_option_params ). Just look for matches in the code and make sure all of them return EXPECTED results. By default, "after" plugins should return the same $result as the original method does. Otherwise, the next plugins will not work correctly, like in your case.
Related
I've tried a lot of suggestions here but it seems like nothing works for me. I am trying to listen to event 'checkout_onepage_controller_success_action'. I am trying to set the order status to 'complete' upon checkout (for now, I commented out that part).
As you may see below (inside the execute method), I am trying to print out the order object then exit. But when testing, nothing happens. No print out, no error message. Nothing...
I ran the following commands before testing:
bin/magento setup:upgrade, bin/magento setup:di:compile, bin/magento cache:clean
I also tried listening to event sales_order_place_after. I also got nothing...
app/code/[company]/[module]/etc/frontend/events.xml
<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="checkout_onepage_controller_success_action">
<observer instance="[company]\[module]\Observer\CheckoutSuccessObserver" name="checkout_onepage_controller_success_action_observer"/>
</event>
</config>
app/code/[company]/[module]/Observer/CheckoutSuccessObserver.php
<?php
namespace [company]\[module]\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Event\Observer;
/**
* Class CheckoutSuccessObserver
*
* #package [company]\[module]\Observer
*/
class CheckoutSuccessObserver implements ObserverInterface
{
/**
* Execute observer
*
* #param \Magento\Framework\Event\Observer $observer
* #return void
*/
public function execute(Observer $observer)
{
$order = $observer->getEvent()->getOrder();
print_r($order); exit;
//$order = $observer->getEvent()->getOrder();
//$order_id = $order->getIncrementId();
//$order = Mage::getModel('sales/order')->loadByIncrementId($order_id);
//$order->setData('state', "complete");
//$order->setStatus("complete");
//$history = $order->addStatusHistoryComment('Order was set to complete by our automation tool.', false);
//$history->setIsCustomerNotified(null);
//$order->save();
}
}
Your sample code looks ok.
Did you enable your custom module? Check it using bin/magento module:status [company]_[module]
Try also to clear the project's directories: https://devdocs.magento.com/guides/v2.3/howdoi/php/php_clear-dirs.html.
I'm using Laravel 5.5. The nature of the website is a 'multisite' architecture where multiple websites/domains are run from the same codebase.
I've come across an issue when sending email. I need to change the from name and address as well as the transport (SMTP, etc) options depending on which website is being viewed. I have these details stored in a config file.
The easiest way is to just pull those details in the Controller before I call Mail::send/Mail::queue and to update them. However, this brings back 2 issues:
There is a heavy reliance on remembering to actually do that every time I send any email in the code. In short, it's not abiding by DRY.
I'd be forced to use Mail::send instead of Mail::queue, because the queue wouldn't have any idea of the config update from the time it was queued only from when it is processed .
How can I achieve what I am looking to do here in a clean way?
I thought about extending all of my 'Mailable' classes with a custom class that updates the SMTP details, but it doesn't look like you can update the SMTP/Transport information after the class is initiated; you can only update the from name and address.
I managed to find a way to do this.
I had my mailable class (ContactFormMailable) extend a custom class, as follows:
<?php
namespace CustomGlobal\Mail;
use CustomGlobal\Mail\CustomMailable;
use CustomGlobal\ContactForm;
class ContactFormMailable extends CustomMailable
{
public $contact_form;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(ContactForm $contact_form)
{
$this->contact_form = $contact_form;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$view = $this->get_custom_mail_view('contact_form', $this->contact_form);
return $this->subject('Contact Form Enquiry')
->view($view);
}
}
You'll notice I'm calling get_custom_mail_view. This is in my extended class and used to calculate the view and template I need to use for my mail, depending on the website being viewed. In here I also set the location of my config folder.
<?php
namespace CustomGlobal\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use Swift_Mailer;
use Swift_SmtpTransport;
use CustomGlobal\Website;
use CustomGlobal\Territory;
class CustomMailable extends Mailable
{
use Queueable, SerializesModels;
public $layout_view_to_serve;
public $host_folder;
/**
* Override Mailable functionality to support per-user mail settings
*
* #param \Illuminate\Contracts\Mail\Mailer $mailer
* #return void
*/
public function send(Mailer $mailer)
{
app()->call([$this, 'build']);
$config = config($this->host_folder .'.mail');
// Set SMTP details for this host
$host = $config['host'];
$port = $config['port'];
$encryption = $config['encryption'];
$transport = new Swift_SmtpTransport( $host, $port, $encryption );
$transport->setUsername($config['username']);
$transport->setPassword($config['password']);
$mailer->setSwiftMailer(new Swift_Mailer($transport));
$mailer->send($this->buildView(), $this->buildViewData(), function ($message) use($config) {
$message->from([$config['from']['address'] => $config['from']['name']]);
$this->buildFrom($message)
->buildRecipients($message)
->buildSubject($message)
->buildAttachments($message)
->runCallbacks($message);
});
}
/**
* Calculate the template we need to serve.
* $entity can be any object but it must contain a
* $website_id and $territory_id, as that is used
* to calculate the path.
*/
public function get_custom_mail_view($view_filename, $entity)
{
if(empty($view_filename)) {
throw new Exception('The get_custom_mail_view method requires a view to be passed as parameter 1.');
}
if(empty($entity->website_id) || empty($entity->territory_id)) {
throw new Exception('The get_custom_mail_view method must be passed an object containing a website_id and territory_id value.');
}
// Get the website and territory
$website = Website::findOrFail($entity->website_id);
$territory = Territory::findOrFail($entity->territory_id);
$view_to_serve = false;
$layout_view_to_serve = false;
// Be sure to replace . with _, as Laravel doesn't play nice with dots in folder names
$host_folder = str_replace('.', '_', $website->website_domain);
$this->host_folder = $host_folder; // Used for mail config later
/***
Truncated for readability. What's in this area isn't really important to this answer.
***/
$this->layout_view_to_serve = $layout_view_to_serve;
return $view_to_serve;
}
}
It's important to remember that mail can be queued. If you do this is another way, such as setting a config at runtime, then you'll find that the process that runs the queue has no visibility/scope of your runtime config changes, and you'll end up firing out email from your default values.
I found a few answers similar to this one, which helped me out, but none of them worked completely, and some are out-dated (Swift_SmtpTransport is changed considerably since those answers).
Hopefully this helps someone else out.
I'm responsible for a rather large web app I built 8 years ago, then later refactored using ZF1 and now trying to move beyond that into more modern framework components. At the moment am trying to see if I can swap out Zend_View for Phalcon\Mvc\View\Simple without having to touch every .phtml file.
Problem I've run into is that while both assign a variable to the view in the same way (e.g. $this->view->foo = 'bar'), in Zend_View in the template you would <?=$this->foo;?> to print the var but in Phalcon it is <?=$foo;?>.
As I mentioned I don't want to go through each of several hundred .phtml files to remove $this->. Is there a way I can override Phalcon's render() or otherwise enable access to the view params using $this?
Here's what I came up with after fiddling with it all day. Simply extend the PHP view engine:
class Engine extends \Phalcon\Mvc\View\Engine\Php
{
/**
* Renders a view using the template engine
*
* #param string $path
* #param array $params
* #param boolean $mustClean
* #return string
*/
public function render($path, $params = null, $mustClean = null)
{
/**
* extract view params into current object scope
* so we can access them with <?=$this->foo;?>
* Maintains backward compat with all the .phtml templates written for Zend_View
*/
foreach($this->_view->getParamsToView() as $key => $val) {
$this->$key = $val;
}
return parent::render($path, $params, $mustClean);
}
You can use DI container to access any registered services in the view, so just put your variables into DI (in the action for example):
public function indexAction()
{
$this->getDi()->set('hello', function() { return 'world'; });
...
And then use it in the template via $this variable:
<div>
<?php echo $this->hello; ?>
</div>
P.S. This is not a good way to assign variables to the view, but should help in your particular case.
I'm have issues trying to get inline tags to methods working, with phpDocumentor version 2.0.0a12. Using the sample code below, no matter what I try (eg /global/foo::bar(), foo::bar, foo::bar() etc) in the {#link parameter} the text gets printed out everytime, instead of parsed as an html anchor tag.
Is anybody else seeing this?
<?php
/**
* File docblock thingy
*/
/**
* Class docblock thingy
*/
class foo{
/**
* Description for bar {#link http://google.ie click for google} this is the inline link
* #return boolean Default true
*/
public function bar(){
return true;
}
/**
* Description for baz {#link foo::bar()}
* #return boolean Default false
*/
public function baz(){
return false;
}
}
Maybe I'm missing a config parameter? The config using for above is:
<?xml version="1.0" encoding="UTF-8" ?>
<phpdoc>
<parser>
<target>.</target>
<default-package-name>Foo</default-package-name>
<parseprivate>on</parseprivate>
</parser>
<transformer>
<target>docs</target>
</transformer>
<files>
<directory>.</directory>
</files>
</phpdoc>
This could be considered a duplicate of PHPDoc inline {#link} (and Netbeans) but I dont' think so because I'm calling phpdoc from command line (not using and IDE).
Any help seriously appreciated ;)
Inline {#link} is not yet implemented in v2 -- http://phpdoc.org/docs/latest/references/phpdoc/tags/link.html
I am trying to register a custom task in my controller in Joomla 3.x so I am modifying the constructor (like in 1.5/2.5) with:
<?php
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
class jjemailControllerjjemail extends JControllerLegacy
{
/**
* constructor (registers additional tasks to methods)
* #return void
*/
public function __construct($config = array())
{
parent::__construct($config);
// Register Extra tasks
$this->registerTask('email, 'email');
}
public function email()
{
$this->setRedirect('index.php?option=com_jjemail&view=thanks', $msg);
}
}
Now if I add a var dump in the constructor before the task registering then that is showing but adding a var dump into the email() function is giving nothing. So I guess I'm failing at registering the task somewhere.
The route calling this looking like: JRoute::_('index.php?option=com_jjemail&task=jjemail.email');
Anyone got any ideas as to why I'm failing in such stupid fashion?
As of Joomla 1.5 you don't need to register default tasks' names.
You only register aliases to map them to one of controller's methods:
$this->registerTask('emailAbc, 'email');
$this->registerTask('unpublish, 'publish');
If you cannot stop execution of the app it would suggest you are calling wrong task from your form/link.
Check your form/link whether it contains a proper task like: option=com_jjemail?task=jjemail.email
Joomla will do all job for you, mapping "jjemail.email" to the email method of your controller