How to Change Order Buttons in Magento 2

How to Change Order Buttons in Magento 2
In the article I am going to answer how to add, remove and modify buttons on the Order view page in Magento 2

Order View Page in the Magento 2 Admin allows you to perform certain actions for a specific Order. Sometimes these actions have to be adjusted based on Merchant's needs and business expectations. In this article we will modify existing Order Buttons for the Order View Page.

Our Goal

For this excercise let's imagine we have the following expectations from a Merchant (this is a real example from real Magento developer life):

  1. We expect to use Invoice button as our next step in order processing flow.
  2. We aren't going to use Hold button since our Orders are imported into ERP (Enterprise Resource Planning) system.
  3. Our integration with Email Marketing platform allows to Send Email notification automatically once an Order is placed.

Looks pretty much clear. Our Magento development skills will help Merchant to achieve these expectations.

Where Should We Start?

First of all we have to navigate to Magento Admin -> Sales -> Orders page and click one of existing Orders from the Orders grid. Page should look simular to mine version: Magento 2 Admin Order Grid Page Pic 1. Magento 2 Admin Order Grid Page

If there are no orders, you might want to create it. URL of Order View page is:

http://magento2.dev/admin/sales/order/view/order_id/1/

From the above URL we may assume that:

  1. The admin/sales/order/view page is managed by the Magento\Sales module.
  2. The sales/order/view URI part has to have a corresponding layout file where all blocks for Order View page are defined.

Let's see. In the Magento\Sales\view\adminhtml\layout directory there is a sales_order_view.xml file.

<referenceContainer name="content">
    <block class="Magento\Sales\Block\Adminhtml\Order\View" name="sales_order_edit"/>
</referenceContainer>

The content container has been used in sales_order_view.xml file to assign Magento\Sales\Block\Adminhtml\Order\View class.

The Magento\Sales\Block\Adminhtml\Order\View class prepares Information Tab content for the Order View page. This class also prepares buttons which we are going to change:

  1. Edit order. Primary page button.
  2. Cancel order.
  3. Send Email.
  4. Creditmemo. The button will be shown once an Invoice is created.
  5. Void payment. Depends on Payment Gateway settings.
  6. Hold order.
  7. Unhold order.
  8. Accept Payment. Shows in case Order Status is in "payment_review".
  9. Deny Payment. Same as 8.
  10. Get Payment Update.
  11. Invoice or Invoice and Ship.
  12. Ship.
  13. Reorder.

These buttons provide full control for an Order: Magento 2 Order Form Buttons Before Pic 2. Magento 2 Order Form Buttons - Before

Mechanics for Changes

All buttons are hard-coded in the Magento\Sales\Block\Adminhtml\Order\View class. There is no container or array of buttons defined in a di.xml configuration file. It means we have to find alternative way of changing order buttons.

The Magento\Sales\Block\Adminhtml\Order\View class adds buttons into buttonList protected variable. The variable is an object of the Magento\Backend\Block\Widget\Button\ButtonList class.

In the Magento\Backend\Block\Widget\Container class (this is parent class for the Magento\Sales\Block\Adminhtml\Order\View class) we may see that the buttonList has been passed into pushButtons() method:

protected function _prepareLayout()
{
    $this->toolbar->pushButtons($this, $this->buttonList);
    return parent::_prepareLayout();
}

We are going to inject our changes right before buttons will be pushed into Forms Toolbar.

This is a good place to extend logic by adding a Plugin for the Toolbar class. The Magento\Sales\Block\Adminhtml\Order\View class does not provide enough methods with public visibility that is why we have to look deeper into the implementation.

Always look for proper ways of extending Magento 2 code, even if it's not obvious - Tweet This

Development

Once we know where to start it is a time to create our new module.

Recommended to Read

How to create Magento 2 Module in a separate repository

Development Design Patterns in Magento 2

Useful Magento Links #1

The following actions are must have for our module:

  1. Create new etc/module.xml configuration file with module name and setup version.
  2. Create registration.php file in order to include module into composer autoload workflow.
  3. Add Pronko_Magento2OrderButtons module into the app/etc/config.php file.
  4. Run bin/magento setup:upgrade command

In order to add a Plugin which will inject Magento\Backend\Block\Widget\Button\Toolbar::pushButtons() method call and provide control over to our Plugin class we have to declare it first. This di.xml configuration file should be placed into the app\code\Pronko\Magento2OrderButtons\etc\adminhtml directory:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Backend\Block\Widget\Button\Toolbar">
        <plugin name="orderFormToolbarButtons" type="Pronko\Magento2OrderButtons\Plugin\Block\Widget\Button\Toolbar" />
    </type>
</config>

The plugin node allows to add an Interceptor class for the Magento\Backend\Block\Widget\Button\Toolbar class. The code has to be regenerated before the configuration will take affect. Content of the <magento_root>/var/generation directory should be removed.

The Pronko\Magento2OrderButtons\Plugin\Block\Widget\Button\Toolbar class might look like the following:

namespace Pronko\Magento2OrderButtons\Plugin\Block\Widget\Button;

use Magento\Backend\Block\Widget\Button\Toolbar as ToolbarContext;
use Magento\Framework\View\Element\AbstractBlock;
use Magento\Backend\Block\Widget\Button\ButtonList;

class Toolbar
{
    /**
     * @param ToolbarContext $toolbar
     * @param AbstractBlock $context
     * @param ButtonList $buttonList
     * @return array
     */
    public function beforePushButtons(
        ToolbarContext $toolbar,
        \Magento\Framework\View\Element\AbstractBlock $context,
        \Magento\Backend\Block\Widget\Button\ButtonList $buttonList
    ) {
        if (!$context instanceof \Magento\Sales\Block\Adminhtml\Order\View) {
            return [$context, $buttonList];
        }
        $buttonList->update('order_edit', 'class', 'edit');
        $buttonList->update('order_invoice', 'class', 'invoice primary');
        $buttonList->update('order_invoice', 'sort_order', (count($buttonList->getItems()) + 1) * 10);

        $buttonList->add('order_review',
            [
                'label' => __('Review'),
                'onclick' => 'setLocation(\'' . $context->getUrl('sales/*/review') . '\')',
                'class' => 'review'
            ]
        );

        $buttonList->remove('order_hold');
        $buttonList->remove('send_notification');

        return [$context, $buttonList];
    }
}

How it works

The beforePushButtons() method listens for the Toolbar::pushButtons() call and triggered before original Toolbar::pushButtons() method call.

Once we are inside the beforePushButtons() method the $context argument has to be checked first. It allows to modify buttonList only when execution is in context of the Magento\Sales\Block\Adminhtml\Order\View class:

if (!$context instanceof \Magento\Sales\Block\Adminhtml\Order\View) {
    return [$context, $buttonList];
}

Once this is checked we have some control over buttonList object prepared in the Magento\Sales\Block\Adminhtml\Order\View::_construct() method. Changing class of Primary Edit button to become secondary button:

$buttonList->update('order_edit', 'class', 'edit');

Updating Invoice button to become a Primary Button:

$buttonList->update('order_invoice', 'class', 'invoice primary');

Since Invoice became Primary Button it has to be moved right side to be consistent with other pages:

$buttonList->update('order_invoice', 'sort_order', (count($buttonList->getItems()) + 1) * 10);

Adding new Review button:

$buttonList->add('order_review',
    [
        'label' => __('Review'),
        'onclick' => 'setLocation(\'' . $context->getUrl('sales/*/review') . '\')',
        'class' => 'review'
    ]
);

And finally, removing Hold and Send Email buttons from the page:

$buttonList->remove('order_hold');
$buttonList->remove('send_notification');

As a result of adding new Plugin we have changed Order View Form Buttons Toolbar:

Magento 2 Order Form Buttons After Pic 3. Magento 2 Order Form Buttons - After

Summary

By looking for better way of extending existing Magento 2 functionality we invest into the future upgrades. There are other ways of achieving Merchant's goal for this task. You may want to post it in the comments below this article.

Follow me @max_pronko on Twitter. I will post more articles and update you once it is ready.

Leave your comments and feedback.

The image for this article is created by great photograph Thomas Hawk.

magento 2 plugins howto

Related Articles

Comments

Next Article Previous Article

LinkedIn Twitter Facebook