Today you’ll learn how to implement a system of admin notifications in Magento 2 B2B yourself. You can do it using Amasty_Blog module that will be helpful for creating custom admin notification for Magento 2.

Task: Implement Admin Notifications on Magento 2

There is a Magento 2 site that sells Magento 2 extensions. Administrator notifications must be implemented to alert clients of module updates.

A built-in functionality can be used to solve this problem. The Magento 2 core contains the module Magento_AdminNotification, which allows administrators to receive notification messages.

The solution for admin notifications will consist of a client part (a client-side module) and a server part (modules for the site that sells the extensions).

The client module will be available with all the other extensions the site sells. The client module will consist of a configuration file, etc/adminhtml/system.xml, of the following form

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../Magento/Backend/etc/system_file.xsd">
    <system>
        <section id="mg_notification" translate="label" type="text" sortOrder="50" showInDefault="1">
            <class>separator-top</class>
            <label>Notification</label>
            <tab>mg_extensions</tab>
            <resource>Web4pro_Notification::config</resource>
            <group id="general" translate="label" type="text" sortOrder="20" showInDefault="1">
                <label>General</label>
                <field id="enabled" translate="label comment" type="select" sortOrder="10" showInDefault="1">
                    <label>Enable Notifications</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="web4pro_url" translate="label comment" type="label" sortOrder="20" showInDefault="1">
                    <label>Web4pro Url</label>
                </field>
            </group>
        </section>
    </system>
</config>

The first configuration parameter allows the admin to turn off notifications, while the second, of the type “label,” will contain the URL where the request for notification messages will be made. This URL will be located in the module file etc/config.xml. Notifications will be requested the whole time the administrator is logged in and is navigating the pages of the administrator section. This is achieved by using the following event handler, which is defined in etc/adminhtml/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="controller_action_predispatch">
        <observer name="web4pro-notification" instance="Web4pro\Notification\Observer\PredispatchAdminActionControllerObserver" />
    </event>
</config>

The event handler code will appear as follows:

class PredispatchAdminActionControllerObserver implements \Magento\Framework\Event\ObserverInterface {

    protected $_feedModel;

    /**
     * @type \Magento\Backend\Model\Auth\Session
     */
    protected $_backendAuthSession;

    public function __construct(
        \Web4pro\Notification\Model\Feed $feed,
        \Magento\Backend\Model\Auth\Session $backendAuthSession
    )
    {
        $this->_feedModel       = $feed;
        $this->_backendAuthSession = $backendAuthSession;
    }

    /**
     * @param \Magento\Framework\Event\Observer $observer
     */
    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        if ($this->_backendAuthSession->isLoggedIn()) {
            $this->_feedModel->checkUpdate();
        }
    }
}

The class \Web4pro\Notification\Model\Feed will appear as follows

class Feed extends \Magento\AdminNotification\Model\Feed {

    /**
     * @inheritdoc
     */
    public function getFeedUrl()
    {
        if ($this->_feedUrl === null) {
            $this->_feedUrl = $this->_backendConfig->getValue('mg_notification/general/web4pro_url');
        }
        return $this->_feedUrl;
    }

    /**
     * @inheritdoc
     */
    public function getLastUpdate()
    {
        return $this->_cacheManager->load('web4pro_notifications_lastcheck');
    }

    /**
     * @inheritdoc
     */
    public function setLastUpdate()
    {
        $this->_cacheManager->save(time(), 'web4pro_notifications_lastcheck');
        return $this;
    }

    public function checkUpdate(){
        if($this->_backendConfig->getValue('mg_notification/general/enabled')){
            return parent::checkUpdate();
        }
        return $this;
    }
}

As you can see, the class is inherited from Magento\AdminNotification\Model\Feed and should implement a method that returns the feed URL and a method for checking for updates, and also allows for reading the time of the last update from the cache and writing it to the cache as well.

Tool: Amasty_Blog Module for Magento 2

The feed URL should return an RSS Feed. Such feed is implemented, for example, in the Amasty_Blog module, and it is returned by the controller rss/feed/index from the Magento_Rss core module. It’s only necessary to specify the feed type from di.xml in the GET parameter “type.” When using the Amasty_Blog module, specify type=amblog, and the parameter record=post must also be specified. In this instance, all active blog posts will be returned to the feed.

Now, if we create a post on the server and open the Magento client admin panel when we go to the admin panel in System Notification, we will see an empty “Severity” field. This is because Amasty_Blog does not have this field, and as a result, it is not in the RSS feed. Therefore, Amasty_Blog will have to be extended to include an additional module that will provide this field. The module will need to add the field “severity” with the type “smallint” to the amasty_blog_posts table during installation. Then, the field will have to be added to the page for editing posts. For this, a layout file, adminhtml/layout/amasty_blog_posts_edit.xml, is needed in the following form

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="content.form_after">
            <block class="Web4pro\Notificationserver\Block\Adminhtml\Form" name="notification.fields"/>
        </referenceBlock>
    </body>
</page>

The form’s class will also need to be implemented

class Form extends \Magento\Backend\Block\Widget\Form\Generic {

    protected function _prepareForm(){
        $model = $this->_coreRegistry->registry('current_amasty_blog_post');
        /** @var \Magento\Framework\Data\Form $form */
        $form = $this->_formFactory->create();
        $form->setHtmlIdPrefix('posts_');

        $fieldset = $form->addFieldset('severity_fieldset', ['legend' => __('Severity')]);

        $fieldset->addField('severity','select',array('name'=>'severity',
                                                      'label'=>__('Severity'),
                                                      'required' => true,
                                                      'values'=>array(''=>__('Please select ...'),
                                                                      1=>__('Critical'),
                                                                      2=>__('Major'),
                                                                      3=>__('Minor'),
                                                                      4=>__('Notice'))));
        $form->setValues($model->getData());
        $this->setForm($form);
        return parent::_prepareForm();
    }
}

This class added the mandatory field Severity to the Content tab of the form for editing posts, and an administrator will indicate the importance of each message added. However, the class responsible for the output of the feed knows nothing about the added field. To solve this problem, we will add a separate type of RSS feed using etc/frontend/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\App\Rss\RssManagerInterface">
        <arguments>
            <argument name="dataProviders" xsi:type="array">
                <item name="notify" xsi:type="string">Web4pro\Notificationserver\Block\Feeds</item>
            </argument>
        </arguments>
    </type>
    <type name="Magento\Rss\Model\Rss">
        <plugin name="notification" type="Web4pro\Notificationserver\Model\Rss" sortOrder="20"/>
    </type>
</config>

The feed class will have the following form

class Feeds extends \Amasty\Blog\Block\Rss\Feeds {

    protected $postsModel;

    public function __construct(
        \Magento\Framework\View\Element\Template\Context $context,
        \Magento\Framework\App\Rss\UrlBuilderInterface $rssUrlBuilder,
        Posts $postsModel,
        \Amasty\Blog\Model\Comments $commentsModel,
        array $data = []
    ) {
        $this->postsModel = $postsModel;
        parent::__construct($context,$rssUrlBuilder,$postsModel,$commentsModel,$data);
    }

    public function getPostsFeed()
    {
        $collection = $this->postsModel->getCollection();

        if (!$this->storeManager->isSingleStoreMode()){
            $collection->addStoreFilter($this->getStoreId());
        }

        $data = ['title' => __('Blog Post Feed'), 'description' => __('Blog Post Feed'), 'link' =>'asd', 'charset' => 'UTF-8'];

        $collection
            ->setDateOrder()
            ->setPageSize(10)
            ->addFieldToFilter('status', Posts::STATUS_ENABLED)
        ;

        foreach ($collection as $post){
            $data['entries'][] = [
                'title'         => $post->getTitle(),
                'link'          => $post->getPostUrl(),
                'description'   => $post->getFullContent(),
                'severity'      => $post->getSeverity(),
                'lastUpdate' 	=> strtotime($post->getUpdatedAt()),
            ];
        }

        return $data;
    }
}

Since the postModel field in the \Amasty\Blog\Block\Rss\Feeds class is private, we had to redefine the constructor. The redefined method getPostsFeed added the severity field to the elements returned. However, there is more that needs to be done to add a field to the feed. The classes in the Zend library Zend_Feed_Builder and Zend_Feed_Rss also know nothing about the added field and do not support the unlimited expansion of fields in a feed. To solve this problem, a plugin was added to the class Magento\Rss\Model\Rss. It will have the following form:

class Rss {

    protected $_dataProvider;

    public function beforeSetDataProvider($model,$dataProvider){
        $this->_dataProvider = $dataProvider;
        return array($dataProvider);
    }

    public function afterCreateRssXml($model,$result){
        if($this->_dataProvider instanceof \Magentice\Notificationserver\Block\Feeds){
            $feed = new Feed(null,null,new Builder($model->getFeeds()));
            return $feed->saveXML();
        }
        return $result;
    }
}

This plugin checks whether the data conductor is a class of the required type. If it is, then it implements the formation of the feed using additional classes. They will be given below.

class Builder extends \Zend_Feed_Builder {

    protected $_entries;

    public function getEntries()
    {
        return $this->_entries;
    }

    protected function _createEntries(array $data)
    {
        foreach ($data as $row) {
            $mandatories = array('title', 'link', 'description');
            foreach ($mandatories as $mandatory) {
                if (!isset($row[$mandatory])) {
                    /**
                     * @see Zend_Feed_Builder_Exception
                     */
                    #require_once 'Zend/Feed/Builder/Exception.php';
                    throw new \Zend_Feed_Builder_Exception("$mandatory key is missing");
                }
            }
            $entry = new \Zend_Feed_Builder_Entry($row['title'], $row['link'], $row['description']);
            if (isset($row['author'])) {
                $entry->setAuthor($row['author']);
            }
            if (isset($row['guid'])) {
                $entry->setId($row['guid']);
            }
            if (isset($row['content'])) {
                $entry->setContent($row['content']);
            }
            if (isset($row['lastUpdate'])) {
                $entry->setLastUpdate($row['lastUpdate']);
            }
            if (isset($row['comments'])) {
                $entry->setCommentsUrl($row['comments']);
            }
            if (isset($row['commentRss'])) {
                $entry->setCommentsRssUrl($row['commentRss']);
            }
            if (isset($row['source'])) {
                $mandatories = array('title', 'url');
                foreach ($mandatories as $mandatory) {
                    if (!isset($row['source'][$mandatory])) {
                        /**
                         * @see Zend_Feed_Builder_Exception
                         */
                        #require_once 'Zend/Feed/Builder/Exception.php';
                        throw new \Zend_Feed_Builder_Exception("$mandatory key of source property is missing");
                    }
                }
                $entry->setSource($row['source']['title'], $row['source']['url']);
            }
            if (isset($row['category'])) {
                $entry->setCategories($row['category']);
            }
            if (isset($row['enclosure'])) {
                $entry->setEnclosures($row['enclosure']);
            }
            if(isset($row['severity'])){
                $entry->offsetSet('severity',$row['severity']);
            }

            $this->_entries[] = $entry;
        }
    }
}
class Feed extends \Zend_Feed_Rss {

    protected function _mapFeedEntries(\DOMElement $root, $array)
    {
        \Zend_Feed::registerNamespace('content', 'http://purl.org/rss/1.0/modules/content/');

        foreach ($array as $dataentry) {
            $item = $this->_element->createElement('item');

            $title = $this->_element->createElement('title');
            $title->appendChild($this->_element->createCDATASection($dataentry->title));
            $item->appendChild($title);

            if (isset($dataentry->author)) {
                $author = $this->_element->createElement('author', $dataentry->author);
                $item->appendChild($author);
            }

            $link = $this->_element->createElement('link', $dataentry->link);
            $item->appendChild($link);

            if (isset($dataentry->guid)) {
                $guid = $this->_element->createElement('guid', $dataentry->guid);
                if (!Zend_Uri::check($dataentry->guid)) {
                    $guid->setAttribute('isPermaLink', 'false');
                }
                $item->appendChild($guid);
            }

            $description = $this->_element->createElement('description');
            $description->appendChild($this->_element->createCDATASection($dataentry->description));
            $item->appendChild($description);

            if (isset($dataentry->content)) {
                $content = $this->_element->createElement('content:encoded');
                $content->appendChild($this->_element->createCDATASection($dataentry->content));
                $item->appendChild($content);
            }

            $pubdate = isset($dataentry->lastUpdate) ? $dataentry->lastUpdate : time();
            $pubdate = $this->_element->createElement('pubDate', date(DATE_RSS, $pubdate));
            $item->appendChild($pubdate);

            if (isset($dataentry->category)) {
                foreach ($dataentry->category as $category) {
                    $node = $this->_element->createElement('category');
                    $node->appendChild($this->_element->createCDATASection($category['term']));
                    if (isset($category['scheme'])) {
                        $node->setAttribute('domain', $category['scheme']);
                    }
                    $item->appendChild($node);
                }
            }

            if (isset($dataentry->source)) {
                $source = $this->_element->createElement('source', $dataentry->source['title']);
                $source->setAttribute('url', $dataentry->source['url']);
                $item->appendChild($source);
            }

            if (isset($dataentry->comments)) {
                $comments = $this->_element->createElement('comments', $dataentry->comments);
                $item->appendChild($comments);
            }
            if (isset($dataentry->commentRss)) {
                $comments = $this->_element->createElementNS('http://wellformedweb.org/CommentAPI/',
                    'wfw:commentRss',
                    $dataentry->commentRss);
                $item->appendChild($comments);
            }

            if (isset($dataentry->enclosure)) {
                foreach ($dataentry->enclosure as $enclosure) {
                    $node = $this->_element->createElement('enclosure');
                    $node->setAttribute('url', $enclosure['url']);
                    if (isset($enclosure['type'])) {
                        $node->setAttribute('type', $enclosure['type']);
                    }
                    if (isset($enclosure['length'])) {
                        $node->setAttribute('length', $enclosure['length']);
                    }
                    $item->appendChild($node);
                }
            }

            if(isset($dataentry->severity)){
                $node = $this->_element->createElement('severity',$dataentry->severity);
                $item->appendChild($node);
            }

            $root->appendChild($item);
        }
    }
}

The rewriting of methods in these classes allowed the “severity” field to be added to the feed. By the way, if the severity is “Critical,” then the client site’s administrator will see a pop-up message in the admin panel, and they will not have to go to the notification page to see the message.

So, now you can implement admin notification messages for Magento 2. If you need any help with this task or you have other questions regarding Custom Magento Development, feel free to contact us. Our Magento 2 developers are ready to create a custom module for you!

5.00 / 5.0
Article rating (1 Reviews)
Do you find this article useful? Please, let us know your opinion and rate the post!
Not badGoodVery GoodGreatAwesome