Building a backend interface

Until now, we have been dealing with setting up general module configuration, e-mail templates, frontend route, frontend layout, block, and template. What remains to complete the module requirements is the admin interface, where the store owner can see submitted tickets and change statuses from open to closed.

The following are needed for a fully functional admin interface as per the requirements:

  • ACL resource used to allow or disallow access to the ticket listing
  • Menu item linking to tickets listing the controller action
  • Route that maps to our admin controller
  • Layout XMLs that map to the ticket listing the controller action
  • Controller action for listing tickets
  • Full XML layout grid definition within layout XMLs defining grid, custom column renderers, and custom dropdown filter values
  • Controller action for closing tickets and sending e-mails to customers

Linking the access control list and menu

We start by adding a new ACL resource entry to the previously defined app/code/Foggyline/Helpdesk/etc/acl.xml file, as a child of the Magento_Backend::admin resource as follows:

<resource id="Magento_Customer::customer">
    <resource id="Foggyline_Helpdesk::ticket_manage" title="Manage Helpdesk Tickets"/>
</resource>

On its own, the defined resource entry does not do anything. This resource will later be used within the menu and controller.

The menu item linking to the tickets listing the controller action is defined under the app/code/Foggyline/Helpdesk/etc/adminhtml/menu.xml file as follows:

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module: Magento_Backend:etc/menu.xsd">
    <menu>
        <add id="Foggyline_Helpdesk::ticket_manage" title="Helpdesk Tickets" module="Foggyline_Helpdesk"
             parent="Magento_Customer::customer" action="foggyline_helpdesk/ticket/index"
             resource="Foggyline_Helpdesk::ticket_manage"/>
    </menu>
</config>

We are using the menu | add element to add a new menu item under the Magento admin area. The position of an item within the admin area is defined by the attribute parent, which in our case means under the existing Customer menu. If the parent is omitted, our item would appear as a new item on a menu. The title attribute value is the label we will see in the menu. The id attribute has to uniquely differentiate our menu item from others. The resource attribute references the ACL resource defined in the app/code/Foggyline/Helpdesk/etc/acl.xml file. If a role of a logged-in user does not allow him to use the Foggyline_Helpdesk::ticket_manage resource, the user would not be able to see the menu item.

Creating routes, controllers, and layout handles

Now we add a route that maps to our admin controller, by defining the app/code/Foggyline/Helpdesk/etc/adminhtml/routes.xml file as follows:

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc /routes.xsd">
    <router id="admin">
        <route id="foggyline_helpdesk" frontName="foggyline_helpdesk">
            <module name="Foggyline_Helpdesk"/>
        </route>
    </router>
</config>

The admin route definition is almost identical to the frontend router definition, where the difference primarily lies in the router ID value, which equals to the admin here.

With the router definition in place, we can now define our three layout XMLs, under the app/code/Foggyline/Helpdesk/view/adminhtml/layout directory, which map to the ticket listing the controller action:

  • foggyline_helpdesk_ticket_grid.xml
  • foggyline_helpdesk_ticket_grid_block.xml
  • foggyline_helpdesk_ticket_index.xml

The reason we define three layout files for a single action controller and not one is because of the way we use the listing in control in the Magento admin area.

The content of the foggyline_helpdesk_ticket_index.xml file is defined as follows:

<?xml version="1.0" encoding="UTF-8"?>

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout /etc/page_configuration.xsd">
    <update handle="formkey"/>
    <update handle="foggyline_helpdesk_ticket_grid_block"/>
    <body>
        <referenceContainer name="content">
            <block class="FoggylineHelpdeskBlock AdminhtmlTicket" name="admin.block.helpdesk.ticket.grid.container">
        </block>
        </referenceContainer>
    </body>
</page>

Two update handles are specified, one pulling in formkey and the other pulling in foggyline_helpdesk_ticket_grid_block. We then reference the content container and define a new block of the FoggylineHelpdeskBlockAdminhtmlTicket class with it.

Utilizing the grid widget

We could have used MagentoBackendBlockWidgetGridContainer as a block class name. However, given that we needed some extra logic, like removing the Add New button, we opted for a custom class that then extends MagentoBackendBlockWidgetGridContainer and adds the required logic.

The FoggylineHelpdeskBlockAdminhtmlTicket class is defined under the app/code/Foggyline/Helpdesk/Block/Adminhtml/Ticket.php file as follows:

<?php

namespace FoggylineHelpdeskBlockAdminhtml;

class Ticket extends MagentoBackendBlockWidgetGridContainer
{
    protected function _construct()
    {
        $this->_controller = 'adminhtml';
        $this->_blockGroup = 'Foggyline_Helpdesk';
        $this->_headerText = __('Tickets');

        parent::_construct();

        $this->removeButton('add');
    }
}

Not much is happening in the Ticket block class here. Most importantly, we extend from MagentoBackendBlockWidgetGridContainer and define _controller and _blockGroup, as these serve as a sort of glue for telling our grid where to find other possible block classes. Since we won't have an Add New ticket feature in admin, we are calling the removeButton method to remove the default Add New button from the grid container.

Back to our second XML layout file, the foggyline_helpdesk_ticket_grid.xml file, which we define as follows:

<?xml version="1.0"?>

<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout /etc/layout_generic.xsd">
    <update handle="formkey"/>
    <update handle="foggyline_helpdesk_ticket_grid_block"/>
    <container name="root">
        <block class="MagentoBackendBlockWidgetGridContainer" name="admin.block.helpdesk.ticket.grid.container" template="Magento_Backend::widget/grid/container /empty.phtml"/>
    </container>
</layout>

Notice how the content of foggyline_helpdesk_ticket_grid.xml is nearly identical to that of foggyline_helpdesk_ticket_index.xml. The only difference between the two is the value of the block class and the template attribute. The block class is defined as MagentoBackendBlockWidgetGridContainer, where we previously defined it as FoggylineHelpdeskBlockAdminhtmlTicket.

If we look at the content of the MagentoBackendBlockWidgetGridContainer class, we can see the following property defined:

protected $_template = 'Magento_Backend::widget/grid/container.phtml';

If we look at the content of the vendor/magento/module-backend/view/adminhtml/templates/widget/grid/container.phtml and vendor/magento/module-backend/view/adminhtml/templates/widget/grid/container/empty.phtml files, the difference can be easily spotted. container/empty.phtml only returns grid HTML, whereas container.phtml returns buttons and grid HTML.

Given that foggyline_helpdesk_ticket_grid.xml will be a handle for the AJAX loading grid listing during sorting and filtering, we need it to return only grid HTML upon reload.

We now move on to the third and largest of XML's layout files, the app/code/Foggyline/Helpdesk/view/adminhtml/layout/foggyline_helpdesk_ticket_grid_block.xml file. Given the size of it, we will split it into two code chunks as we explain them one by one.

The first part, or initial content of the foggyline_helpdesk_ticket_grid_block.xml file, is defined as follows:

<?xml version="1.0" encoding="UTF-8"?>

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout /etc/page_configuration.xsd">
    <body>
        <referenceBlock name= "admin.block.helpdesk.ticket.grid.container">
            <block class="MagentoBackendBlockWidgetGrid" name="admin.block.helpdesk.ticket.grid" as="grid">
                <arguments>
                    <argument name="id" xsi:type="string"> ticketGrid</argument>
                    <argument name="dataSource" xsi:type="object"> FoggylineHelpdeskModelResourceModel TicketCollection
                    </argument>
                    <argument name="default_sort" xsi:type="string">ticket_id</argument>
                    <argument name="default_dir" xsi:type="string">desc</argument>
                    <argument name="save_parameters_in_session" xsi:type="boolean">true</argument>
                    <argument name="use_ajax" xsi:type="boolean">true</argument>
                </arguments>
                <block class="MagentoBackendBlock WidgetGridColumnSet" name= "admin.block.helpdesk.ticket.grid.columnSet" as="grid.columnSet">
                    <!-- Column definitions here -->
                </block>
            </block>
        </referenceBlock>
    </body>
</page>

Notice <!-- Column definitions here -->; we will come back to that soon. For now, let's analyze what is happening here. Right after a body element, we have a reference to admin.block.helpdesk.ticket.grid.container, which is a content block child defined under the foggyline_helpdesk_ticket_grid.xml and foggyline_helpdesk_ticket_index.xml files. Within this reference, we are defining another block of class MagentoBackendBlockWidgetGrid, passing it a name of our choosing and an alias. Further, this block has an arguments list and another block of class MagentoBackendBlockWidgetGridColumnSet as child elements.

Through the arguments list we specify the:

  • id: Set to the value of ticketGrid, we can set any value we want here, ideally sticking to formula {entity name}.
  • dataSource: Set to the value of FoggylineHelpdeskModelResourceModelTicketCollection, which is the name of our Ticket entity resource class.
  • default_sort: Set to the value of ticket_id, which is the property of the Ticket entity by which we want to sort.
  • default_dir: Set to the value of desc, to denote a descending order of sorting. This value functions together with default_sort as a single unit.
  • save_parameters_in_session: Set to true, this is easiest to explain using the following example: if we do some sorting and filtering on the Ticket grid and then move on to another part of the admin area, then come back to Ticket grid, if this value is set to yes, the grid we see will have those filters and sorting set.
  • use_ajax: Set to true, when grid filtering and sorting is triggered, an AJAX loader kicks in and reloads only the grid area and not the whole page.

Right after the grid blocks argument list, we have the grid column set. This brings us to the second part of foggyline_helpdesk_ticket_grid_block.xml content. We simply replace the <!-- Columns here --> comment with the following:

<block class="MagentoBackendBlockWidgetGridColumn" as="ticket_id">
    <arguments>
        <argument name="header" xsi:type="string" translate="true">ID</argument>
        <argument name="type" xsi:type="string">number</argument>
        <argument name="id" xsi:type="string">ticket_id</argument>
        <argument name="index" xsi:type="string">ticket_id</argument>
    </arguments>
</block>
<block class="MagentoBackendBlockWidgetGridColumn" as="title">
    <arguments>
        <argument name="header" xsi:type="string" translate="true">Title</argument>
        <argument name="type" xsi:type="string">string</argument>
        <argument name="id" xsi:type="string">title</argument>
        <argument name="index" xsi:type="string">title</argument>
    </arguments>
</block>
<block class="MagentoBackendBlockWidgetGridColumn" |  as="severity">
    <arguments>
        <argument name="header" xsi:type="string" translate="true">Severity</argument>
        <argument name="index" xsi:type="string">severity</argument>
        <argument name="type" xsi:type="string">options</argument>
        <argument name="options" xsi:type="options" model="FoggylineHelpdeskModelTicketGridSeverity"/>
        <argument name="renderer" xsi:type="string"> FoggylineHelpdeskBlockAdminhtmlTicketGridRenderer Severity
        </argument>
        <argument name="header_css_class" xsi:type="string"> col-form_id</argument>
        <argument name="column_css_class" xsi:type="string"> col-form_id</argument>
    </arguments>
</block>
<block class="MagentoBackendBlockWidgetGridColumn" as="status">
    <arguments>
        <argument name="header" xsi:type="string" translate="true">Status</argument>
        <argument name="index" xsi:type="string">status</argument>
        <argument name="type" xsi:type="string">options</argument>
        <argument name="options" xsi:type="options"
                  model="FoggylineHelpdeskModelTicket GridStatus"/>
        <argument name="renderer" xsi:type="string"> FoggylineHelpdeskBlockAdminhtmlTicketGrid RendererStatus
        </argument>
        <argument name="header_css_class" xsi:type="string"> col-form_id</argument>
        <argument name="column_css_class" xsi:type="string"> col-form_id</argument>
    </arguments>
</block>
<block class="MagentoBackendBlockWidgetGridColumn" as="action">
    <arguments>
        <argument name="id" xsi:type="string">action</argument>
        <argument name="header" xsi:type="string" translate="true">Action</argument>
        <argument name="type" xsi:type="string">action</argument>
        <argument name="getter" xsi:type="string">getId</argument>
        <argument name="filter" xsi:type="boolean">false</argument>
        <argument name="sortable" xsi:type="boolean">false</argument>
        <argument name="actions" xsi:type="array">
            <item name="view_action" xsi:type="array">
                <item name="caption" xsi:type="string" translate="true">Close</item>
                <item name="url" xsi:type="array">
                    <item name="base" xsi:type="string">*/*/close</item>
                </item>
                <item name="field" xsi:type="string">id</item>
            </item>
        </argument>
        <argument name="header_css_class" xsi:type="string"> col-actions</argument>
        <argument name="column_css_class" xsi:type="string"> col-actions</argument>
    </arguments>
</block>

Similar to grid, column definitions also have arguments that define its look and behavior:

  • header: Mandatory, the value we want to see as a label on top of the column.
  • type: Mandatory, can be anything from: date, datetime, text, longtext, options, store, number, currency, skip-list, wrapline, and country.
  • id: Mandatory, a unique value that identifies our column, preferably matching the name of the entity property.
  • index: Mandatory, the database column name.
  • options: Optional, if we are using a type like options, then for the options argument we need to specify the class like FoggylineHelpdeskModelTicketGridSeverity that implements MagentoFrameworkOptionArrayInterface, meaning it provides the toOptionArray method that then fills the values of options during grid rendering.
  • renderer: Optional, as our Ticket entities store severity and status as integer values in the database, columns would render those integer values into columns, which is not really useful. We want to turn those integer values into labels. In order to do so, we need to rewrite the rendering bit of a single table cell, which we do with the help of the renderer argument. The value we pass to it, FoggylineHelpdeskBlockAdminhtmlTicketGridRendererSeverity, needs to be a class that extends MagentoBackendBlockWidgetGridColumnRendererAbstractRenderer and does its own implementation of the render method.
  • header_css_class: Optional, if we prefer to specify a custom header class.
  • column_css_class: Optional, if we prefer to specify a custom column class.

Creating a grid column renderer

The FoggylineHelpdeskBlockAdminhtmlTicketGridRendererSeverity class, defined in the app/code/Foggyline/Helpdesk/Block/Adminhtml/Ticket/Grid/Renderer/Severity.php file, is as follows:

<?php

namespace FoggylineHelpdeskBlockAdminhtmlTicketGridRenderer;

class Severity extends MagentoBackendBlockWidgetGrid ColumnRendererAbstractRenderer
{
    protected $ticketFactory;

    public function __construct(
        MagentoBackendBlockContext $context,
        FoggylineHelpdeskModelTicketFactory $ticketFactory,
        array $data = []
    )
    {
        parent::__construct($context, $data);
        $this->ticketFactory = $ticketFactory;
    }

    public function render(MagentoFrameworkDataObject $row)
    {
        $ticket = $this->ticketFactory->create()->load($row-> getId());

        if ($ticket && $ticket->getId()) {
            return $ticket->getSeverityAsLabel();
        }

        return '';
    }
}

Here, we are passing the instance of the ticket factory to the constructor and then using that instance within the render method to load a ticket based on the ID value fetched from the current row. Given that $row->getId() returns the ID of the ticket, this is a nice way to reload the entire ticket entity and then fetch the full label from the ticket model by using $ticket->getSeverityAsLabel(). Whatever string we return from this method is what will be shown under the grid row.

Another renderer class that is referenced within the foggyline_helpdesk_ticket_grid_block.xml file is FoggylineHelpdeskBlockAdminhtmlTicketGridRendererStatus, and we define its content under the app/code/Foggyline/Helpdesk/Block/Adminhtml/Ticket/Grid/Renderer/Status.php file as follows:

<?php

namespace FoggylineHelpdeskBlockAdminhtmlTicketGridRenderer;

class Status extends MagentoBackendBlockWidgetGridColumn RendererAbstractRenderer
{
    protected $ticketFactory;

    public function __construct(
        MagentoBackendBlockContext $context,
        FoggylineHelpdeskModelTicketFactory $ticketFactory,
        array $data = []
    )
    {
        parent::__construct($context, $data);
        $this->ticketFactory = $ticketFactory;
    }

    public function render(MagentoFrameworkDataObject $row)
    {
        $ticket = $this->ticketFactory->create()->load($row-> getId());

        if ($ticket && $ticket->getId()) {
            return $ticket->getStatusAsLabel();
        }

        return '';
    }
}

Given that it too is used for a renderer, the content of the Status class is nearly identical to the content of the Severity class. We pass on the ticket factory object via the constructor, so we have it internally for usage within the render method. Then we load the Ticket entity using the ticket factory and ID value fetched from a $row object. As a result, the column will contain the label value of a status and not its integer value.

Creating grid column options

Besides referencing renderer classes, our foggyline_helpdesk_ticket_grid_block.xml file also references the options class for the Severity field.

We define the FoggylineHelpdeskModelTicketGridSeverity options class under the app/code/Foggyline/Helpdesk/Model/Ticket/Grid/Severity.php file as follows:

<?php

namespace FoggylineHelpdeskModelTicketGrid;

class Severity implements MagentoFrameworkOptionArrayInterface
{
    public function toOptionArray()
    {
        return FoggylineHelpdeskModel Ticket::getSeveritiesOptionArray();
    }
}

The options value from XML layouts refers to a class that has to implement the toOptionArray method, which returns an array of arrays, such as the following example:

return [
    ['value'=>'theValue1', 'theLabel1'],
    ['value'=>'theValue2', 'theLabel2'],
];

Our Severity class simply calls the static method we have defined on the Ticket class, the getSeveritiesOptionArray, and passes along those values.

Creating controller actions

Up to this point, we have defined the menu item, ACL resource, XML layouts, block, options class, and renderer classes. What remains to connect it all are controllers. We will need three controller actions (Index, Grid, and Close), all extending from the same admin Ticket controller.

We define the admin Ticket controller under the app/code/Foggyline/Helpdesk/Controller/Adminhtml/Ticket.php file as follows:

<?php

namespace FoggylineHelpdeskControllerAdminhtml;

class Ticket extends MagentoBackendAppAction
{
    protected $resultPageFactory;
    protected $resultForwardFactory;
    protected $resultRedirectFactory;

    public function __construct(
        MagentoBackendAppActionContext $context,
        MagentoFrameworkViewResultPageFactory $resultPageFactory,
        MagentoBackendModelViewResultForwardFactory $resultForwardFactory
    )
    {
        $this->resultPageFactory = $resultPageFactory;
        $this->resultForwardFactory = $resultForwardFactory;
        $this->resultRedirectFactory = $context-> getResultRedirectFactory();
        parent::__construct($context);
    }

    protected function _isAllowed()
    {
        return $this->_authorization-> isAllowed('Foggyline_Helpdesk::ticket_manage');
    }

    protected function _initAction()
    {
        $this->_view->loadLayout();
        $this->_setActiveMenu(
            'Foggyline_Helpdesk::ticket_manage'
        )->_addBreadcrumb(
            __('Helpdesk'),
            __('Tickets')
        );
        return $this;
    }
}

There are a few things to note here. $this->resultPageFactory, $this->resultForwardFactory and $this->resultRedirectFactory are objects to be used on the child (Index, Grid, and Close), so we do not have to initiate them in each child class separately.

The _isAllowed() method is extremely important every time we have a custom-defined controller or controller action that we want to check against our custom ACL resource. Here, we are the isAllowed method call on the MagentoFrameworkAuthorizationInterface type of object ($this->_authorization). The parameter passed to the isAllowed method call should be the ID value of our custom ACL resource.

We then have the _initAction method, which is used for setting up logic shared across child classes, usually things like loading the entire layout, setting up the active menu flag, and adding breadcrumbs.

Moving forward, we define the Index controller action within the app/code/Foggyline/Helpdesk/Controller/Adminhtml/Ticket/Index.php file as follows:

<?php

namespace FoggylineHelpdeskControllerAdminhtmlTicket;

class Index extends FoggylineHelpdeskControllerAdminhtmlTicket
{
    public function execute()
    {
        if ($this->getRequest()->getQuery('ajax')) {
            $resultForward = $this->resultForwardFactory-> create();
            $resultForward->forward('grid');
            return $resultForward;
        }
        $resultPage = $this->resultPageFactory->create();

        $resultPage-> setActiveMenu('Foggyline_Helpdesk::ticket_manage');
        $resultPage->getConfig()->getTitle()-> prepend(__('Tickets'));

        $resultPage->addBreadcrumb(__('Tickets'), __('Tickets'));
        $resultPage->addBreadcrumb(__('Manage Tickets'), __('Manage Tickets'));

        return $resultPage;
    }
}

Controller actions execute within their own class, within the execute method. Our execute method first checks if the coming request is the AJAX parameter within it. If there is an AJAX parameter, the request is forwarded to the Grid action of the same controller.

If there is no AJAX controller, we simply create the instance of the MagentoFrameworkViewResultPageFactory object, and set title, active menu item, and breadcrumbs in it.

A logical question at this point would be how does all of this work and where can we see it. If we log in to the Magento admin area, under the Customers menu we should be able to see the Helpdesk Tickets menu item. This item, defined previously within app/code/Foggyline/Helpdesk/etc/adminhtml/menu.xml, says the menu action attribute equals to foggyline_helpdesk/ticket/index, which basically translates to the Index action of our Ticket controller.

Once we click on the Helpdesk Tickets link, Magento will hit the Index action within its Ticket controller and try to find the XML file that has the matching route {id}+{controller name }+{controller action name }+{xml file extension }, which in our case translates to {foggyline_helpdesk}+{ticket}+{index}+{.xml}.

At this point, we should be able to see the screen, as shown in the following screenshot:

Creating controller actions

However, if we now try to use sorting or filtering, we would get a broken layout. This is because based on arguments defined under the foggyline_helpdesk_ticket_grid_block.xml file, we are missing the controller Grid action. When we use sorting or filtering, the AJAX request hits the Index controller and asks to be forwarded to the Grid action, which we haven't defined yet.

We now define the Grid action within the app/code/Foggyline/Helpdesk/Controller/Adminhtml/Ticket/Grid.php file as follows:

<?php

namespace FoggylineHelpdeskControllerAdminhtmlTicket;

class Grid extends FoggylineHelpdeskControllerAdminhtmlTicket
{
    public function execute()
    {
        $this->_view->loadLayout(false);
        $this->_view->renderLayout();
    }
}

There is only one execute method with the Grid controller action class, which is expected. The code within the execute method simply calls the loadLayout(false) method to prevent the entire layout loading, making it load only the bits defined under the foggyline_helpdesk_ticket_grid.xml file. This effectively returns the grid HTML to the AJAX, which refreshes the grid on the page.

Finally, we need to handle the Close action link we see on the grid. This link was defined as part of the column definition within the foggyline_helpdesk_ticket_grid_block.xml file and points to */*/close, which translates to "router as relative from current URL / controller as relative from current URL / close action", which further equals to our Ticket controller Close action.

We define the Close controller action under the app/code/Foggyline/Helpdesk/Controller/Adminhtml/Ticket/Close.php file as follows:

<?php

namespace FoggylineHelpdeskControllerAdminhtmlTicket;

class Close extends FoggylineHelpdeskControllerAdminhtmlTicket
{
    protected $ticketFactory;
    protected $customerRepository;
    protected $transportBuilder;
    protected $inlineTranslation;
    protected $scopeConfig;
    protected $storeManager;

    public function __construct(
        MagentoBackendAppActionContext $context,
        MagentoFrameworkViewResultPageFactory $resultPageFactory,
        MagentoBackendModelViewResultForwardFactory $resultForwardFactory,
        FoggylineHelpdeskModelTicketFactory $ticketFactory,
        MagentoCustomerApiCustomerRepositoryInterface $customerRepository,
        MagentoFrameworkMailTemplateTransportBuilder $transportBuilder,
        MagentoFrameworkTranslateInlineStateInterface $inlineTranslation,
        MagentoFrameworkAppConfigScopeConfigInterface $scopeConfig,
        MagentoStoreModelStoreManagerInterface $storeManager
    )
    {
        $this->ticketFactory = $ticketFactory;
        $this->customerRepository = $customerRepository;
        $this->transportBuilder = $transportBuilder;
        $this->inlineTranslation = $inlineTranslation;
        $this->scopeConfig = $scopeConfig;
        $this->storeManager = $storeManager;
        parent::__construct($context, $resultPageFactory, $resultForwardFactory);
    }

    public function execute()
    {
        $ticketId = $this->getRequest()->getParam('id');
        $ticket = $this->ticketFactory->create()->load($ticketId);

        if ($ticket && $ticket->getId()) {
            try {
                $ticket->setStatus(Foggyline HelpdeskModelTicket::STATUS_CLOSED);
                $ticket->save();
                $this->messageManager->addSuccess(__('Ticket successfully closed.'));

                /* Send email to customer */
                $customer = $this->customerRepository-> getById($ticket->getCustomerId());
                $storeScope = MagentoStoreModel ScopeInterface::SCOPE_STORE;
                $transport = $this->transportBuilder
                    ->setTemplateIdentifier($this->scopeConfig-> getValue('foggyline_helpdesk/email_template /customer', $storeScope))
                    ->setTemplateOptions(
                        [
                            'area' => MagentoFrameworkAppArea::AREA_ADMINHTML,
                            'store' => $this->storeManager- >getStore()->getId(),
                        ]
                    )
                    ->setTemplateVars([
                        'ticket' => $ticket,
                        'customer_name' => $customer-> getFirstname()
                    ])
                    ->setFrom([
                        'name' => $this->scopeConfig-> getValue('trans_email/ident_general /name', $storeScope),
                        'email' => $this->scopeConfig-> getValue('trans_email/ident_general /email', $storeScope)
                    ])
                    ->addTo($customer->getEmail())
                    ->getTransport();
                $transport->sendMessage();
                $this->inlineTranslation->resume();
                $this->messageManager->addSuccess(__('Customer notified via email.'));

            } catch (Exception $e) {
                $this->messageManager->addError(__('Error with closing ticket action.'));
            }
        }

        $resultRedirect = $this->resultRedirectFactory->create();
        $resultRedirect->setPath('*/*/index');

        return $resultRedirect;
    }
}

The Close action controller has two separate roles to fulfill. One is to change the ticket status; the other is to send an e-mail to the customer using the proper e-mail template. The class constructor is being passed a lot of parameters that all instantiate the objects we will be juggling around.

Within the execute action, we first check for the existence of the id parameter and then try to load a Ticket entity through the ticket factory, based on the provided ID value. If the ticket exists, we set its status label to FoggylineHelpdeskModelTicket::STATUS_CLOSED and save it.

Following the ticket save is the e-mail-sending code, which is very similar to the one that we already saw in the customer New Ticket save action. The difference is that the e-mail goes out from the admin user to the customer this time. We are setting the template ID to the configuration value at path foggyline_helpdesk/email_template/customer. The setTemplateVars method is passed to the member array this time, both ticket and customer_name, as they are both used in the e-mail template. The setFrom method is passed the general store username and e-mail, and the sendMessage method is called on the transport object.

Finally, using the resultRedirectFactory object, the user is redirected back to the tickets grid.

With this, we finalize our module functional requirement.

Though we are done with the functional requirement of a module, what remains for us as developers is to write tests. There are several types of tests, such as unit, functional, integration, and so on. To keep things simple, within this chapter we will cover only unit tests across a single model class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.104.29