Browsed by
Month: December 2016

How to Create a Category Image Attribute in Magento 2

How to Create a Category Image Attribute in Magento 2

Creating a custom Category Attribute with an image upload is quite common feature requirement in our shops.
So this blog-post will be about the steps you have to take to create a custom category attribute with an image upload in Magento 2.
The post turned out to be quite long, but I wanted to provide a complete description of all steps necessary. So stay with us if your interested 🙂

Our Module is called Dev98_CategoryAttributes and the image attribute will be called dev98_icon.

Creating the Attribute

In our projects we are creating the attributes using Install- or UpdateData classes, except when the attributes are explicitly managed manually.
So to create thedev98_icon we might write something like this.

After running the setup:upgrade command to upgrade the application database and schema, we have created the attribute and should be able to store information for this attribute.

Extending the Category Form

To get an file upload for our attribute we need to extend the ui-component for the category form.
The category form is defined in a category_form.xml, which can be extend by adding the following code to the file
Dev98/CategoryAttributes/view/adminhtml/ui_component/category_form.xml

The above code block defines a new fieldset dev98_attributes which will contain one fielddev98_icon.
There is one parameter for the field dev98_icon I want to point out, which is theuploaderConfig array.
In this array we have a parameter called url which defines the ActionController which is responsible for handling the image upload.
As you can see we have provided a custom ActionController for handling the upload, because after taking a closer look to the Controller Magento uses for it’s category image upload, you will see why.
The file is \Magento\Catalog\Controller\Adminhtml\Category\Image\Upload

The Magento ActionController for handling the image upload is hardwired against the attribute image. There is no reasonable way we can reuse this code, so we will create our own in the next step.

Image Upload ActionController

Basically we have copied the code from Magento and refactored it to be more extensible:

I left out the constructor and the _isAllowed method for a better readability.

Our ActionController expects one argument attribute_code and hands that to the imageUploader which will handle the upload of the image.
Now we have a reusable ActionController if we have to provide more custom category images.
And as it turned out in one of our projects, we did need more than one.
Furthermore we might extract that code into a vendor module.

Current Status

√ Create the Attribute
√ Create category_form field
√ Create the Controller handling the upload

We have already done quite some work but we are not done yet.

If you try running the code created so far you might see an Image Upload and the Image will be uploaded to the tmp folder media/catalog/category/dev98/tmp/.
But it will remain there and there will be nothing saved to our dev98_icon attribute for this category.

So why is that?

The data for our dev98_icon field in the request is provided as an array. And as there is nothing changed regarding that fact, we end up with nothing being saved to our attribute.
Futhermore, we need to handle the move from the tmp directory to the final image destination ourselves.
That is OK in my opinion as there might be an error during the category save and we only want the image to be moved to its final destination when the save was successful.

Category Save Observers

To do so we need to observe the following two events:

  • catalog_category_prepare_save
  • catalog_category_save_after

In our Observer for catalog_category_prepare_save we will convert the attribute image array to a string and in the catalog_category_save_after we will move the image from the tmp folder to its destination.

Dev98\CategoryAttributes\Observer\Category\CategoryImageDataPrepare

The execute method looks rather complicated, but what it basically does is to set the image-name to the category model and mark the category as  is_uploaded so later-on we can detect we have to move an image. This code is mostly copied from the Magento Core, because as it turned out this part is also not reusable – yet.

Current Status 2

√ Create the Attribute
√ Create category_form field
√ Create the Controller handling the upload
√ Observe the CategoryPrepareSave to get image name saved
√ Observe the CategoryAfterSave to get image moved to destination

But now we are done you might think.

Well. No. There are still further steps to take to get this working completely.

At this moment your image upload and saving will be working, but after refreshing the page you will see there is still something wrong as the image is either not shown at all or a placeholder image is shown.

Plugin Category DataProvider

After some debugging you will find out that there is class called  \Magento\Catalog\Model\Category\DataProvider which is used to provide the category data for the form.
In this class we will find a method getData which does the image preparation for the standard image, again in a non extensible way:

Next up we will create a Plugin that integrates after the  getData method and does the preparation for our custom attributes.

Let’s create the di.xml with the Plugin class called
Dev98\CategoryAttributes\Plugin\Category\CategoryDataProviderPlugin

And finally our Plugin class itself:

As you can see we extracted the url generation of the icon to a CategoryUrlRepository as this funcationality is need within more classes and in other modules as well.

So last but not least here is the CategoryUrlRepository:

That was the final class we needed to create to get this image upload working through out.

Full Checklist

Here is a short Summary of the classes and files created.

√ Create the Attribute
√ Create category_form field
√ Create the Controller handling the upload
√ Observe the CategoryPrepareSave to get image name saved
√ Observe the CategoryAfterSave to get image moved to destination
√ Create Plugin for Category\DataProvider to prepare Image Data for Frontend

Final Thoughts

If you have read this far, thanks for bearing with us on this one.
We really spend some time debugging the different issues we had while creating the image upload.
Multiple times we weren’t quite sure whether the error was in the javascript part or server-side.
Sometimes we had to even debug javascript code to find out what was wrong with the implementation on the server-side, as there where errors that were not shown or the data was not provided as needed.

But it was worth the effort and we know have a good glimpse of how the Category Admin is implemented.

The code is still not perfect and might need some further polishing, but it should give you a good starting point.
Right now I am thinking about putting together a public module on github to share this implementation.

If you are interested in the full code of the module please let me know.
And if there is an easier way to achieve these please let me know as well.

 

Magento1 since 2008 / Magento2 since 2015
Passionate Road Bike Rider (~3.500km/yr)
Loves building software with a elaborate architecture and design
3x Magento Certified
Software Developer >10 years
Head of Magento Development @ netz98
How to create attribute-options in Magento2

How to create attribute-options in Magento2

Recently we had to create Configurable Products in our Product Import.
To create those products we had to make sure that all simple products are generated before the configurable products and that all the attribute options for our configurable attribute are available.
So this post will focus on how to create those attribute options and a problem where we had to spend some time figuring out.

Preface

We need to create those attribute options before we call
$this->magentoImporter->validateSource($dataSource);
otherwise the validation will fail because the options are validated as well.

So let’s assume we have a collection of attribute-options that we want to ensure for an attribute.

Creating the Options

We will iterate this array and generate an Option Data-Object for each of the attribute-options.
This will be done using the Service Contract from Magento_Eav with the following classes:

  • Magento\Eav\Api\AttributeOptionManagementInterface (used to create the option)
  • Magento\Eav\Api\Data\AttributeOptionInterface  (one option)
  • Magento\Eav\Api\Data\AttributeOptionInterfaceFactory  (used as $this->optionFactory, to create an Option Data-Object)

The resulting code might look something like this.

After running this peace of code you will get attribute-options for your attribute.
Note: This code does not check whether an attribute-option is already in the database.

Running the Import

Now when you start you product import using the Magento Core functionality your configurable products will be created and the simple items will be assigned properly.

What I left out until now, was the trouble we had finding out how to create the option correctly.
So the next chapter will be about the issue we face, how we solved it and based on that what not to do when creating an option.

Problem with generating the Options

At first we tried generating our options like this:

As it turns this will fail with an Foreign-Key Constraint Violation while inserting the Option,
basically saying the referenced option_id cannot be found.

After some intense debugging I found out what was going on.
Inside the OptionManagement the option->getValue()  is used to define the option_id if it is set.
\Magento\Eav\Model\Entity\Attribute\OptionManagement::getOptionId

Debugging the Generation of an Option further we end up in the following method
\Magento\Eav\Model\ResourceModel\Entity\Attribute::_updateAttributeOption

This method gets the before mentioned option_id aka option->getValue() injected, does an int-cast and based on that decides whether it is an update or insert.

So if we come through this method with our sample having $optionId set to “20mm”
the int-cast will return 20 and assume that the option is available and an update is to be made.
That is the main reason for the mySQL Foreign-Key Exception.

Why does it work with ‘new_option’?

In case we leave out the $option->setValue()  in our implementation
the optionId will be set to ‘new_option’ as shown above.

The int-cast of the string ‘new_option’ will be 0 and therefore it will be detected as an insert.

Conclusion

It would have been great if Magento had some kind of validation as a first instance within the OptionManagement implementation, like throwing an Exception when the option->getValue()  is not an int. Or validating that the provided option_id is valid.

Finally I want to point out, that you should NOT call  $option->setValue()unless you have the exact option_id for this Option available.

I hope this post is helpful and will save you some trouble when generating custom options programmatically.

If you had similar experience or have something to add, feel free to leave a comment. Your feedback is always welcome.

Magento1 since 2008 / Magento2 since 2015
Passionate Road Bike Rider (~3.500km/yr)
Loves building software with a elaborate architecture and design
3x Magento Certified
Software Developer >10 years
Head of Magento Development @ netz98
How to adjust increment-ids in Magento 2

How to adjust increment-ids in Magento 2

Maybe almost every Magento developer has had the task to customize the increment-ids for orders or customers in Magento.

Recap Magento 1

In Magento 1 you had to change the column increment_prefix  in the table eav_entity_store .
I am sure there are modules out there that let you achieve that in convenient way.
We have done that by using Setup-scripts most of the time.
When a new store is created you need set the increment_prefix afterwards and do so for all entities that you need and that are defined in eav_entity_type.
So far an rather straight forward task I would say.

What has changed in Magento 2

Let’s take a look at the eav_entity_store  table.

In this database an order and a rma has already been created.
But as you can see the order and customer entity no longer is maintained within this table.

In Magento 2 the increment-id generation mechanism basically has not changed for certain entities. The increment-id generation for customers and RMAs still uses the eav_entity_store .

The sales module has received some changes regarding the generation of those increment-ids.

Let’s have a look at what changed in Magento2 regarding the order increment-id generation.
There are two new tables that you may not have seen yet, them being sales_sequence_meta  and sales_sequence_profile .
In the example below you see the content of those for the default website and store setup.

Table sales_sequence_meta

There is a table called sales_sequence_meta  which defines store-specific sequence-tables for the sales entity-types.

Table sales_sequence_profile

Then we have a table sales_sequence_profile  that contains the configuration for the increment-id generation.

Most of the columns are pretty self-explanatory.
You can define a prefix  and suffix  that is added to the beginning respectively the ending of the generated increment-id.
With the start_value  you can define the first increment-id.
The step  column defines the increments that are made between increment-ids.

So to change the prefix for the order increment-ids we need to change to column prefix to the desired value, “DEVMWR” in this case.
This will result in an order increment-id like DEVMWR000000004

After that change the orders will receive a custom increment-id.

If you have any notes feel free to leave a comment.

Magento1 since 2008 / Magento2 since 2015
Passionate Road Bike Rider (~3.500km/yr)
Loves building software with a elaborate architecture and design
3x Magento Certified
Software Developer >10 years
Head of Magento Development @ netz98
Creating Configurable Products

Creating Configurable Products

Basically there are different ways for creating Configurable Products in Magento2 (like in Magento1).
You can create it using the Admin Panel, using a Setup\InstallData or Setup\UpgradeData class or during a custom import.

ADDING THE ATTRIBUTE

Let’s assume we are creating our attribute using the Setup\UpgradeData class in our module.
The code we might initially create could look something like the following:

The above code will create an attribute with the attribute-code ‘dev98_diameter’, which is available for simple and configurable products, within the group ‘General’ and is searchable, filterable etc… Futhermore we are adding some attribute options as well.

CREATING A CONFIGURABLE PRODUCT

Now if you add this attribute to an attribute-set and try creating a configurable product for this attribute it won’t work out as you might expect.
You will not see the attribute in the overlay ‘Create Product Configurations’  where you choose the attributes you want your product to be configurable with.

In Magento 2 the Admin Edit Form for Products changed quite a bit.

The most important change is, that the type of a product e.g. simple, configurable, etc. is no longer selected but determined automatically when saving the product.

So the product type you select using the DropDown in the Admin Product Grid is obsolete and not really used.

IMPROVING OUR UPDATEDATA CLASS

Knowing that the product is virtual at the moment of creating our configurable product, we have to add to type ‘virtual’ to the apply_to parameter in our ‘Setup\UpgradeData’ class.

After running the ‘setup:upgrade’ command, flushing the cache and reloading our Admin Product Edit Form we can now see the attribute ‘dev98_diameter’ in the ‘Create Product Configurations’ overlay.

From here-on we can create configurable products with our attribute as we please.

SUMMARY

To sum it up in short, you have to make your product attribute apply to type virtual to create configurable products.
At least when you are creating you attributes programmatically like in InstallData/ UpgradeData or using a attributes import.

Magento1 since 2008 / Magento2 since 2015
Passionate Road Bike Rider (~3.500km/yr)
Loves building software with a elaborate architecture and design
3x Magento Certified
Software Developer >10 years
Head of Magento Development @ netz98