Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GUI to Edit EAV Attributes & Sets - Customer #2352

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

justinbeaty
Copy link
Contributor

@justinbeaty justinbeaty commented Aug 1, 2022

Description (*)

[WIP] This commit was previously part of #2317. It is broken out so that we can discuss ways to integrate the customer EAV attributes into customer groups, and customer forms. Discussions about this are in that PR.

Related Pull Requests

#2317
#2260

Fixed Issues (if relevant)

Manual testing scenarios (*)

Questions or comments

Todo:

  • Check translation strings
  • Modify customer attributes to add use_in_forms option
  • Modify Customers > Manage Groups to allow us to select an attribute set for each customer group (both customer and address set)

Contribution checklist (*)

  • Pull request has a meaningful description of its purpose
  • All commits are accompanied by meaningful commit messages
  • All automated tests passed successfully (all builds are green)
  • Add yourself to contributors list

@github-actions github-actions bot added Component: Adminhtml Relates to Mage_Adminhtml Component: Catalog Relates to Mage_Catalog Component: Customer Relates to Mage_Customer Component: Eav Relates to Mage_Eav documentation Template : admin Relates to admin template translations Relates to app/locale labels Aug 1, 2022
@justinbeaty
Copy link
Contributor Author

In testing the use_in_forms function, it seems like adding attributes to any of the frontend forms does not work, and would involve editing template files as mentioned by fballiano here: #2317 (comment)

However it does seem to work perfectly in all of the adminhtml areas.

@fballiano
Copy link
Contributor

I can confirm you that there's some work on the frontend that needs to be done for the use_in_forms to work :-)

*
* @return array
*/
public function toOptionArray()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have custom form types, is it possible to define these in app\code\core\Mage\Customer\etc\config.xml?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it maybe be better to set up an event to hook into? I'm thinking about the best way a module could define their own form types.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using event is ok, but I like the config method. An example is product types:

<catalog>
<product>
<type>
<simple translate="label" module="catalog">
<label>Simple Product</label>
<model>catalog/product_type_simple</model>
<composite>0</composite>
<index_priority>10</index_priority>
</simple>

and product custom option:

<options>
<custom>
<groups>
<text translate="label" module="adminhtml">
<label>Text</label>
<render>adminhtml/catalog_product_edit_tab_options_type_text</render>
<types>

which I have used to add more options in the HTML select element.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kiatng thanks for the examples, I was thinking about it more and agree the config.xml method is indeed better.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ref

class Mage_Adminhtml_Model_System_Config_Source_Product_Options_Type

I made a sample code:

class Mage_Adminhtml_Model_System_Config_Source_Customer_Form
{
    const CUSTOMER_FORM_PATH = 'global/customer/form';

    public function toOptionArray()
    {
        $helper = Mage::helper('customer');
        foreach (Mage::getConfig()->getNode(self::CUSTOMER_FORM_PATH)->children() as $form) {
            $labelPath = self::CUSTOMER_FORM_PATH . '/' . $form->getName() . '/label';
            $forms[] = [
                'label' => $helper->__((string) Mage::getConfig()->getNode($labelPath)),
                'value' => $form->getName()
            ];
        }

        return $forms;
    }
}

The config.xml looks like this:

    <global>
        <customer>
            <form>
                <adminhtml_checkout translate="label" module="customer">
                    <label>Adminhtml Checkout</label>
                </adminhtml_checkout>
                <adminhtml_customer translate="label" module="customer">
                    <label>Adminhtml Customer</label>
                </adminhtml_customer>
                <!-- more forms -->
            </form>
        </customer>
    </global>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That code sample doesn't consider the module attribute. Corrected version:

class Mage_Adminhtml_Model_System_Config_Source_Customer_Form
{
    const CUSTOMER_FORM_PATH = 'global/customer/form';

    public function toOptionArray()
    {
        foreach (Mage::getConfig()->getNode(self::CUSTOMER_FORM_PATH)->children() as $form) {
            $labelPath = self::CUSTOMER_FORM_PATH . '/' . $form->getName() . '/label';
            $moduleName = $form->getAttribute('module') ?? 'customer';
            $forms[] = [
                'label' => Mage::helper($moduleName)->__((string) Mage::getConfig()->getNode($labelPath)),
                'value' => $form->getName()
            ];
        }

        return $forms;
    }
}

Note that $form->getAttribute('module') can return null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kiatng thanks for the implementation!

Do you think this XML is better? Since we need to also define customer_address forms it keeps all forms under one node, <form>.

<global>
   <form>
      <customer>
         <adminhtml_checkout translate="label" module="customer">
            <label>Adminhtml Checkout</label>
         </adminhtml_checkout>
         <adminhtml_customer translate="label" module="customer">
            <label>Adminhtml Customer</label>
         </adminhtml_customer>
         <!-- more forms -->
      </customer>
      <customer_address>
         <adminhtml_customer_address translate="label" module="customer">
            <label>Adminhtml Customer Address</label>
         </adminhtml_customer_address>
         <customer_address_edit translate="label" module="customer">
            <label>Customer Address Edit</label>
         </customer_address_edit>
         <!-- more forms -->
      </customer_address>
   </form>
</global>

I could also move the source models to:

Mage_Adminhtml_Model_System_Config_Source_Form_Customer
Mage_Adminhtml_Model_System_Config_Source_Form_Customer_Address

to match.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ref

class Mage_Customer_Model_Form extends Mage_Eav_Model_Form

and

</catalog>
<eav_attributes>

Use the module name like <catalog>:

<global>
   <eav>
     <form>
        <customer>
           <!-- more forms -->
        </customer>
        <customer_address>
           <!-- more forms -->
        </customer_address>
     </form>
   </eav>
</global>

or continue with the format <eav_attributes>:

<global>
   <eav_forms>
      <customer>
         <!-- more forms -->
      </customer>
      <customer_address>
         <!-- more forms -->
      </customer_address>
   </eav_forms>
</global>

Which do you prefer? I'd go with the latter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the latter as well 👍

@github-actions github-actions bot added JavaScript Relates to js/* Template : base Relates to base template Template : rwd Relates to rwd template labels Aug 2, 2022
@justinbeaty
Copy link
Contributor Author

justinbeaty commented Aug 2, 2022

I've started support for use_in_forms to work on the frontend without the user having to edit templates:

image

Todo:

  • Compile the SCSS to make the date field show up correctly.
  • Date field if required doesn't pass validation even if filled 🤔
  • Add changes in app/design/frontend/rwd/default/template/customer/form/edit.phtml to app/design/frontend/base as well
  • Support the other frontend forms

@lgtm-com
Copy link

lgtm-com bot commented Aug 2, 2022

This pull request introduces 2 alerts and fixes 8 when merging e629678 into cf82b8f - view on LGTM.com

new alerts:

  • 2 for Syntax error

fixed alerts:

  • 4 for Useless assignment to local variable
  • 2 for Missing variable declaration
  • 1 for Useless conditional
  • 1 for Unused variable, import, function or class

@fballiano
Copy link
Contributor

wow!

@justinbeaty
Copy link
Contributor Author

justinbeaty commented Aug 2, 2022

Working on the other frontend forms, checkout_register is a bit crowded, but it's just how Magento is with checkout & register I guess. I think a store owner should decide to not add too many fields to this form.

image

@github-actions github-actions bot added Component: Checkout Relates to Mage_Checkout Component: Persistant Relates to Mage_Persistant labels Aug 2, 2022
@justinbeaty
Copy link
Contributor Author

I still have address forms to do tomorrow.

I am also thinking it might be nice to group these fields on the frontend based on the attribute set > groups. This way there is some more organization and the user can label the fieldset. For example in Account Information they could have a group called "Social Media" and let the user enter urls to their pages (just an example.)

But also, I am going to take a stab at allowing you to define attribute set per customer group. Thus it would be important that not only do you define use_in_forms but also put that attribute in a attribute set. Otherwise you cannot control what attributes show up for each customer group.

@justinbeaty
Copy link
Contributor Author

justinbeaty commented Aug 3, 2022

@kiatng (and @fballiano) I'm seeking your opinion on the following (originally posted here)


I use attribute sets to organize the attributes for different customer groups. The project has over 200 customer attributes, each customer group having very different set of attributes.

My concern is that changes to EAV could make it very difficult to merge OM to existing projects with custom EAV.

The PR as it now should definitely not conflict with anything since it's all new code, but I see your point if we hook up a customer group -> customer attribute set relation. Still, I think it could be worth it maybe just waiting to merge this PR until the next major version.

How did you correlate attribute sets with customer groups? My first thought is to modify the customer_group table:

mysql> describe customer_group;
+---------------------+-------------------+------+-----+---------+----------------+
| Field               | Type              | Null | Key | Default | Extra          |
+---------------------+-------------------+------+-----+---------+----------------+
| customer_group_id   | smallint unsigned | NO   | PRI | NULL    | auto_increment |
| customer_group_code | varchar(32)       | NO   |     | NULL    |                |
| tax_class_id        | int unsigned      | NO   |     | 0       |                |
+---------------------+-------------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

To add something like customer_attribute_set_id and maybe even customer_address_attribute_set_id. Probably both are useful in the case you want to control both attribute sets per group.

If we can do it in a way that is maybe compatible with what you've done, then that's at least one site that wouldn't break.


In any case, I'll set up an observer so that users can change this logic.

@kiatng
Copy link
Contributor

kiatng commented Aug 5, 2022

@kiatng So are you able to create two attribute sets that apply to the same customer group, but differ in the form setting? In other words, you can show different fields for the same customer group on different forms?

Yes, in fact sets 19 and 23 in the screenshot I attached are of the same customer group. The use case is for different backend users to view/edit a different set of attributes. At its height, there were a couple hundreds of backend users in this project.

There are other advantages to add apply_to at the attribute set level. Because it is upstream (EAV level), there is less coding downstream (customer level and other custom entity).

Your approach to match an attribute set at the customer group is similar to the product's attribute_set_id. However, number of customer groups (I have seen max 30 groups) is much less than the number of products (can be hundreds of thousands). It's easier to configure apply_to at the attribute set level for customer groups, but impractical for products.

But what I have is customized to a very particular use case. I only developed what is required of the project, I do not have anything on addresses and categories. Developing for general use cases is a lot more challenging.

@fballiano fballiano changed the base branch from 20.0 to main April 4, 2023 17:34
@luigifab
Copy link
Contributor

Will be ready for RC4?

@fballiano
Copy link
Contributor

I wanted to revive this, I think they should target "next" but it's like my preference, because we're more free to choose how to act on the frontend.

Copy link
Contributor

@empiricompany empiricompany left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check if extrafileds exists and add validation classes to text input
I wasn't able to send a commit

@@ -153,6 +153,18 @@
</li>
</ul>
</div>
<div class="fieldset">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrap into

<?php if($this->extraFields()): ?>
...
<?php endif ?>

<?php $_code = $_attribute->getAttributeCode() ?>
<label for="<?php echo $this->getFieldId($_code)?>"<?php if ($this->isRequired()) echo ' class="required"' ?>><?php if ($this->isRequired()) echo '<em>*</em>' ?><?php echo $this->__($_attribute->getStoreLabel()) ?></label>
<div class="input-box">
<input type="text" id="<?php echo $this->getFieldId($_code)?>" name="<?php echo $this->getFieldName($_code)?>" value="<?php echo $this->escapeHtml($this->getObject()->getData($_code)) ?>" title="<?php echo Mage::helper('core')->quoteEscape($this->__($_attribute->getStoreLabel())) ?>" class="input-text" <?php echo $this->getFieldParams() ?> />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add validation classes
class="input-text <?php echo $_attribute->getFrontendClass() ?>"

@empiricompany
Copy link
Contributor

i've made some test on this PR:

  • for each type of attribute (custom/address), I can't set the scope value. It's always stuck on 'store view', and in the grid column, it's always null.
  • in the frontend checkout, the extra fields are not being printed

@ADDISON74 ADDISON74 self-assigned this Jul 14, 2023
@github-actions github-actions bot removed the Component: Catalog Relates to Mage_Catalog label Jul 29, 2023
@fballiano
Copy link
Contributor

there were some bad conflicts, I did what i could, not sure 100% was correct

justinbeaty added a commit to justinbeaty/maho that referenced this pull request Sep 28, 2024
justinbeaty added a commit to justinbeaty/maho that referenced this pull request Sep 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Component: Adminhtml Relates to Mage_Adminhtml Component: Checkout Relates to Mage_Checkout Component: Customer Relates to Mage_Customer Component: Eav Relates to Mage_Eav Component: Persistant Relates to Mage_Persistant documentation Don't forget this PR JavaScript Relates to js/* new feature Template : admin Relates to admin template Template : base Relates to base template Template : rwd Relates to rwd template translations Relates to app/locale
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants