Add website filter to product export

Today I will be focusing to extend the export product functionality by adding a website filter.

This original functionality can be seen by visiting the administrator’s:

System -> Data Transfer -> Export

p1

Assuming you have a basic module already set (out of scope of this article),
adding a website filter can be achieved, in theory, by going through these two steps:

  • Override the form to have a select option of website ids
  • Override the model that actually prepares the collection whose data will be send to a .csv file

Eventually, we will have this look available

p2


First we need to add new field to the field set.

The original field set is created in

https://gist.github.com/marjan7790/06a22fe29355acd192c5907eddb99e30

For those of you who wonder why this is the class that we need, consider these following hints:

  • the admin url is /admin/export/ which to file system will translate into Controller/Adminhtml/Export/Index.php
  • given the controller path, the layout handle on filesystem will translate into view/adminhtml/layout/adminhtml_export_index.xml
  • there should not be too many files named adminhtml_export_index.xml in a default Magento module that may have “import” or “export” as part of its name

Anyway, the _prepareForm() should be overridden by adding

https://gist.github.com/marjan7790/52c963d0c7e9e24531d119164aeea1ef

We will start by telling Magento that we have preference over the original form. We will do this in etc/ di.xml by adding

https://gist.github.com/marjan7790/c3e64a2e86b3429391bf7253ae3700c7

Now lets create our file Form.php and add the _prepareForm() method as

https://gist.github.com/marjan7790/b7d120725382dbc31a5afdc9232f1490

However, we still need a protected property $websiteFactory
and we also need to override the __construct method so we can get an instance from the Website Factory object.

So, add this code in your class

https://gist.github.com/marjan7790/fc0e849f832357945615cb82868f9599

You might need to re-compile after this. After that, lets reopen our page and you should see the website selector.


Now, for “Entity Type” select “Products”, for “Website” the website of your choice and lets inspect the “Continue” button. You will notice that it leads to a link like admin/export/export.

This given, lets try to capture the website id selection in Magento\ImportExport\Controller\Adminhtml\Export\Export. Directly in this file (and you will revert code to original later ) add these lines inside the execute() method like

https://gist.github.com/marjan7790/3f828641a48bee2bdeddb42ad680733f

Then, set a break point and execute. You will notice:

$post becomes an array that has these keys: “form_key”, “frontend_label”, “attribute_code” and “export_filter” which is array on its own, 82 members long.
$params becomes an array that has the same keys $post has plus: “entity”, “file_format”, “fields_enclosure”.

Hey, this is good. We see in the $params that collected value from the FORM elements. But, sadly, not our “website_id”.
What gives?

Open, the layout handle adminhtml_export_index.xml in view/adminhtml/layout and look for this line

https://gist.github.com/marjan7790/1d659a55fab191466a51272d030264ff

Ok, that given, lets open file before.phtml, located in view/adminhtml/templates/export/form. This file extends the VarienExport.prototype by adding at least a couple of methods, out of which, interesting for us is

https://gist.github.com/marjan7790/4351fd28608371703201b5b8c6ae7889

right after which there is something like varienExport = new VarienExport();

This varienExport JS object is actually called on change on the “Entity Type” selection. Go back to the Form.php and notice the onchange value.

But, what about the larger picture? Magento uses RequireJs to load a component and execute it immediatelly. And all of this is done inside a .phtml file?
Magento certainly can find a way to surprise me. One should always try to be both aware of such subtleties and to simultaneously stay sane 🙂

Anyways, knowing this, we need to override the before.phtml now. Create new file adminhtml_export_index.xml inside view/adminhtml/layout and add this content

https://gist.github.com/marjan7790/2b6aabba3c7281a4da52321fa1003b5c

Later, create before.phtml inside view/adminhtml/templates and add this content

https://gist.github.com/marjan7790/4ad063bc41e1b59668565ddfd1a255de

I have the full code practically copied over just to add one small change inside getFile()

https://gist.github.com/marjan7790/fd8f6b41df9941f525737639c29bd86f

I don’t like this. Dear reader, please suggest an improvement?

Anyway, lets continue and again, for “Entity Type” select “Products”, for “Website” the website of your choice and lets click the “Continue” button. Sure enough, now we have a website_id inside the $params array. And, you can revert to the original code now and, for clarity, clear any break points.

Lets now continue to override the model that fetches the data.


With all said earlier, open Magento\ImportExport\Controller\Adminhtml\Export\Export and focus on this code

https://gist.github.com/marjan7790/ee04a8b841897c319ccd5ce63c6c2ba3

$model is of type \Magento\ImportExport\Model\Export and after this line

https://gist.github.com/marjan7790/f90678d8c8578462bc8034a288dd8f87

the protected property _data will have encapsulated whatever came from the getParams() from the Request object.

For those of you who wonder what happens here, the chain of inheritance is
\Magento\ImportExport\Model\Export extends \Magento\ImportExport\Model\AbstractModel extends \Magento\Framework\DataObject, and the last one, DataObject, produces the setData() method.

Back to what the controller does

https://gist.github.com/marjan7790/0a6008c7987908795c1a0a3312dc2aa3

So, we turn our attention to $model->export(). Open class Magento\ImportExport\Model\Export and you will see within the export()

https://gist.github.com/marjan7790/a418a0ef04146a53fa2c716abb4a1f5f

This line actually calls the export() method from Magento\CatalogImportExport\Model\Export\Product and within there is something like

https://gist.github.com/marjan7790/3488f1615889c437314b19b0685e7770

Now, we could override at two places

a. accept website_id

https://gist.github.com/marjan7790/37a77f22ce00e173ed83c572922e7996

b. add website filter

https://gist.github.com/marjan7790/1b47ec68a4fab007e258cbe028fa3347

Now, that should do it. So lets go ahead and edit etc/di.xml and add

https://gist.github.com/marjan7790/15b079ad2947dd47567593a839a9d538

And create the following files with the content from below

https://gist.github.com/marjan7790/ea6c314fba3d8ad8a554e62bfc72f988

and

https://gist.github.com/marjan7790/a93b070ae92166953807e13441b93c9f

Recompile if you must, but I guess this should do it.

Eventually, you can pick up the source code from

https://github.com/marjan7790/datatransfer

Finally, you are encouraged on your own to continue add an option to download data from all websites at once, as the original function was.  Notice, we don’t have such option now.


Conclusion

Adding new behavior to some admin functionality in this case meant

  • overriding the Form class
  • overriding .phtml file that executes RequireJS module
    for this step we needed to add our own layout handle, i.e. orverride how the handle works, and, we needed to override the RequireJS module
  • overriding two Models from two different modules that would do the actual data export

Usually, one might expect to even override a Controller class, but there it goes, we went into overriding RequireJS module.
Regardless, keep in mind that it will be unlikely to achieve any similar requirements without overriding at least one Block and one Model class, whichever they are.

Thank you for reading.