classes/XLite/Model/Attribute.php line 690

Open in your IDE?
  1. <?php
  2. /**
  3.  * Copyright (c) 2001-present X-Cart Holdings LLC. All rights reserved.
  4.  * See https://www.x-cart.com/license-agreement.html for license details.
  5.  */
  6. namespace XLite\Model;
  7. use ApiPlatform\Core\Annotation as ApiPlatform;
  8. use Doctrine\ORM\Mapping as ORM;
  9. use Doctrine\ORM\PersistentCollection;
  10. use XLite\API\Endpoint\Attribute\Checkbox\DTO\AttributeCheckboxInput as InputCheckbox;
  11. use XLite\API\Endpoint\Attribute\Checkbox\DTO\AttributeCheckboxOutput as OutputCheckbox;
  12. use XLite\API\Endpoint\Attribute\Hidden\DTO\AttributeHiddenInput as InputHidden;
  13. use XLite\API\Endpoint\Attribute\Hidden\DTO\AttributeHiddenOutput as OutputHidden;
  14. use XLite\API\Endpoint\Attribute\Select\DTO\AttributeSelectInput as InputSelect;
  15. use XLite\API\Endpoint\Attribute\Select\DTO\AttributeSelectOutput as OutputSelect;
  16. use XLite\API\Endpoint\Attribute\Text\DTO\AttributeTextInput as InputText;
  17. use XLite\API\Endpoint\Attribute\Text\DTO\AttributeTextOutput as OutputText;
  18. use XLite\API\Endpoint\ProductAttribute\Checkbox\DTO\ProductAttributeCheckboxInput as ProductAttributeInputCheckbox;
  19. use XLite\API\Endpoint\ProductAttribute\Checkbox\DTO\ProductAttributeCheckboxOutput as ProductAttributeOutputCheckbox;
  20. use XLite\API\Endpoint\ProductAttribute\Select\DTO\ProductAttributeSelectInput as ProductAttributeInputSelect;
  21. use XLite\API\Endpoint\ProductAttribute\Select\DTO\ProductAttributeSelectOutput as ProductAttributeOutputSelect;
  22. use XLite\API\Endpoint\ProductAttribute\Text\DTO\ProductAttributeTextInput as ProductAttributeInputText;
  23. use XLite\API\Endpoint\ProductAttribute\Text\DTO\ProductAttributeTextOutput as ProductAttributeOutputText;
  24. use XLite\Core\Cache\ExecuteCachedTrait;
  25. use XLite\Core\Database;
  26. use XLite\Model\AttributeValue\AttributeValueSelect;
  27. use XLite\Model\Repo\ARepo;
  28. use XLite\Model\AttributeValue\AAttributeValue;
  29. /**
  30.  * @ORM\Entity
  31.  * @ORM\Table (name="attributes")
  32.  * @ApiPlatform\ApiResource(
  33.  *     itemOperations={
  34.  *          "get_text"={
  35.  *              "method"="GET",
  36.  *              "path"="/attributes_text/{id}.{_format}",
  37.  *              "identifiers"={"id"},
  38.  *              "input"=InputText::class,
  39.  *              "output"=OutputText::class,
  40.  *              "openapi_context"={
  41.  *                  "summary"="Retrieve a global textarea attribute",
  42.  *                  "parameters"={
  43.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  44.  *                  }
  45.  *              }
  46.  *          },
  47.  *          "put_text"={
  48.  *              "method"="PUT",
  49.  *              "path"="/attributes_text/{id}.{_format}",
  50.  *              "identifiers"={"id"},
  51.  *              "input"=InputText::class,
  52.  *              "output"=OutputText::class,
  53.  *              "openapi_context"={
  54.  *                  "summary"="Update a global textarea attribute",
  55.  *                  "parameters"={
  56.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  57.  *                  }
  58.  *              }
  59.  *          },
  60.  *          "delete_text"={
  61.  *              "method"="DELETE",
  62.  *              "path"="/attributes_text/{id}.{_format}",
  63.  *              "identifiers"={"id"},
  64.  *              "input"=InputText::class,
  65.  *              "output"=OutputText::class,
  66.  *              "openapi_context"={
  67.  *                  "summary"="Delete a global textarea attribute",
  68.  *                  "parameters"={
  69.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  70.  *                  }
  71.  *              }
  72.  *          },
  73.  *          "get_checkbox"={
  74.  *              "method"="GET",
  75.  *              "path"="/attributes_checkbox/{id}.{_format}",
  76.  *              "identifiers"={"id"},
  77.  *              "input"=InputCheckbox::class,
  78.  *              "output"=OutputCheckbox::class,
  79.  *              "openapi_context"={
  80.  *                  "summary"="Retrieve a global yes/no attribute",
  81.  *                  "parameters"={
  82.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  83.  *                  }
  84.  *              }
  85.  *          },
  86.  *          "put_checkbox"={
  87.  *              "method"="PUT",
  88.  *              "path"="/attributes_checkbox/{id}.{_format}",
  89.  *              "identifiers"={"id"},
  90.  *              "input"=InputCheckbox::class,
  91.  *              "output"=OutputCheckbox::class,
  92.  *              "openapi_context"={
  93.  *                  "summary"="Update a global yes/no attribute",
  94.  *                  "parameters"={
  95.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  96.  *                  }
  97.  *              }
  98.  *          },
  99.  *          "delete_checkbox"={
  100.  *              "method"="DELETE",
  101.  *              "path"="/attributes_checkbox/{id}.{_format}",
  102.  *              "identifiers"={"id"},
  103.  *              "input"=InputCheckbox::class,
  104.  *              "output"=OutputCheckbox::class,
  105.  *              "openapi_context"={
  106.  *                  "summary"="Delete a global yes/no attribute",
  107.  *                  "parameters"={
  108.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  109.  *                  }
  110.  *              }
  111.  *          },
  112.  *          "get_select"={
  113.  *              "method"="GET",
  114.  *              "path"="/attributes_select/{id}.{_format}",
  115.  *              "identifiers"={"id"},
  116.  *              "input"=InputSelect::class,
  117.  *              "output"=OutputSelect::class,
  118.  *              "openapi_context"={
  119.  *                  "summary"="Retrieve a global plain field attribute",
  120.  *                  "parameters"={
  121.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  122.  *                  }
  123.  *              }
  124.  *          },
  125.  *          "put_select"={
  126.  *              "method"="PUT",
  127.  *              "path"="/attributes_select/{id}.{_format}",
  128.  *              "identifiers"={"id"},
  129.  *              "input"=InputSelect::class,
  130.  *              "output"=OutputSelect::class,
  131.  *              "openapi_context"={
  132.  *                  "summary"="Update a global plain field attribute",
  133.  *                  "parameters"={
  134.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  135.  *                  }
  136.  *              }
  137.  *          },
  138.  *          "delete_select"={
  139.  *              "method"="DELETE",
  140.  *              "path"="/attributes_select/{id}.{_format}",
  141.  *              "identifiers"={"id"},
  142.  *              "input"=InputSelect::class,
  143.  *              "output"=OutputSelect::class,
  144.  *              "openapi_context"={
  145.  *                  "summary"="Delete a global plain field attribute",
  146.  *                  "parameters"={
  147.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  148.  *                  }
  149.  *              }
  150.  *          },
  151.  *          "get_hidden"={
  152.  *              "method"="GET",
  153.  *              "path"="/attributes_hidden/{id}.{_format}",
  154.  *              "identifiers"={"id"},
  155.  *              "input"=InputHidden::class,
  156.  *              "output"=OutputHidden::class,
  157.  *              "openapi_context"={
  158.  *                  "summary"="Retrieve a global hidden attribute",
  159.  *                  "parameters"={
  160.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  161.  *                  }
  162.  *              }
  163.  *          },
  164.  *          "put_hidden"={
  165.  *              "method"="PUT",
  166.  *              "path"="/attributes_hidden/{id}.{_format}",
  167.  *              "identifiers"={"id"},
  168.  *              "input"=InputHidden::class,
  169.  *              "output"=OutputHidden::class,
  170.  *              "openapi_context"={
  171.  *                  "summary"="Update a global hidden attribute",
  172.  *                  "parameters"={
  173.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  174.  *                  }
  175.  *              }
  176.  *          },
  177.  *          "delete_hidden"={
  178.  *              "method"="DELETE",
  179.  *              "path"="/attributes_hidden/{id}.{_format}",
  180.  *              "identifiers"={"id"},
  181.  *              "input"=InputHidden::class,
  182.  *              "output"=OutputHidden::class,
  183.  *              "openapi_context"={
  184.  *                  "summary"="Delete a global hidden attribute",
  185.  *                  "parameters"={
  186.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  187.  *                  }
  188.  *              }
  189.  *          },
  190.  *          "product_class_based_get_text"={
  191.  *              "method"="GET",
  192.  *              "path"="/product_classes/{class_id}/attributes_text/{id}.{_format}",
  193.  *              "identifiers"={"id"},
  194.  *              "input"=InputText::class,
  195.  *              "output"=OutputText::class,
  196.  *              "openapi_context"={
  197.  *                  "summary"="Retrieve a product class textarea attribute",
  198.  *                  "parameters"={
  199.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  200.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  201.  *                  }
  202.  *              }
  203.  *          },
  204.  *          "product_class_based_put_text"={
  205.  *              "method"="PUT",
  206.  *              "path"="/product_classes/{class_id}/attributes_text/{id}.{_format}",
  207.  *              "identifiers"={"id"},
  208.  *              "input"=InputText::class,
  209.  *              "output"=OutputText::class,
  210.  *              "openapi_context"={
  211.  *                  "summary"="Update a product class textarea attribute",
  212.  *                  "parameters"={
  213.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  214.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  215.  *                  }
  216.  *              }
  217.  *          },
  218.  *          "product_class_based_delete_text"={
  219.  *              "method"="DELETE",
  220.  *              "path"="/product_classes/{class_id}/attributes_text/{id}.{_format}",
  221.  *              "identifiers"={"id"},
  222.  *              "input"=InputText::class,
  223.  *              "output"=OutputText::class,
  224.  *              "openapi_context"={
  225.  *                  "summary"="Delete a product class textarea attribute",
  226.  *                  "parameters"={
  227.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  228.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  229.  *                  }
  230.  *              }
  231.  *          },
  232.  *          "product_class_based_get_checkbox"={
  233.  *              "method"="GET",
  234.  *              "path"="/product_classes/{class_id}/attributes_checkbox/{id}.{_format}",
  235.  *              "identifiers"={"id"},
  236.  *              "input"=InputCheckbox::class,
  237.  *              "output"=OutputCheckbox::class,
  238.  *              "openapi_context"={
  239.  *                  "summary"="Retrieve a product class yes/no attribute",
  240.  *                  "parameters"={
  241.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  242.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  243.  *                  }
  244.  *              }
  245.  *          },
  246.  *          "product_class_based_put_checkbox"={
  247.  *              "method"="PUT",
  248.  *              "path"="/product_classes/{class_id}/attributes_checkbox/{id}.{_format}",
  249.  *              "identifiers"={"id"},
  250.  *              "input"=InputCheckbox::class,
  251.  *              "output"=OutputCheckbox::class,
  252.  *              "openapi_context"={
  253.  *                  "summary"="Update a product class yes/no attribute",
  254.  *                  "parameters"={
  255.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  256.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  257.  *                  }
  258.  *              }
  259.  *          },
  260.  *          "product_class_based_delete_checkbox"={
  261.  *              "method"="DELETE",
  262.  *              "path"="/product_classes/{class_id}/attributes_checkbox/{id}.{_format}",
  263.  *              "identifiers"={"id"},
  264.  *              "input"=InputCheckbox::class,
  265.  *              "output"=OutputCheckbox::class,
  266.  *              "openapi_context"={
  267.  *                  "summary"="Delete a product class yes/no attribute",
  268.  *                  "parameters"={
  269.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  270.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  271.  *                  }
  272.  *              }
  273.  *          },
  274.  *          "product_class_based_get_select"={
  275.  *              "method"="GET",
  276.  *              "path"="/product_classes/{class_id}/attributes_select/{id}.{_format}",
  277.  *              "identifiers"={"id"},
  278.  *              "input"=InputSelect::class,
  279.  *              "output"=OutputSelect::class,
  280.  *              "openapi_context"={
  281.  *                  "summary"="Retrieve a product class plain field attribute",
  282.  *                  "parameters"={
  283.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  284.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  285.  *                  }
  286.  *              }
  287.  *          },
  288.  *          "product_class_based_put_select"={
  289.  *              "method"="PUT",
  290.  *              "path"="/product_classes/{class_id}/attributes_select/{id}.{_format}",
  291.  *              "identifiers"={"id"},
  292.  *              "input"=InputSelect::class,
  293.  *              "output"=OutputSelect::class,
  294.  *              "openapi_context"={
  295.  *                  "summary"="Update a product class plain field attribute",
  296.  *                  "parameters"={
  297.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  298.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  299.  *                  }
  300.  *              }
  301.  *          },
  302.  *          "product_class_based_delete_select"={
  303.  *              "method"="DELETE",
  304.  *              "path"="/product_classes/{class_id}/attributes_select/{id}.{_format}",
  305.  *              "identifiers"={"id"},
  306.  *              "input"=InputSelect::class,
  307.  *              "output"=OutputSelect::class,
  308.  *              "openapi_context"={
  309.  *                  "summary"="Delete a product class plain field attribute",
  310.  *                  "parameters"={
  311.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  312.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  313.  *                  }
  314.  *              }
  315.  *          },
  316.  *          "product_based_get_text"={
  317.  *              "method"="GET",
  318.  *              "path"="/products/{product_id}/attributes_text/{id}.{_format}",
  319.  *              "identifiers"={"id"},
  320.  *              "input"=ProductAttributeInputText::class,
  321.  *              "output"=ProductAttributeOutputText::class,
  322.  *              "openapi_context"={
  323.  *                  "summary"="Retrieve a product-specific textarea attribute",
  324.  *                  "parameters"={
  325.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  326.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  327.  *                  }
  328.  *              }
  329.  *          },
  330.  *          "product_based_put_text"={
  331.  *              "method"="PUT",
  332.  *              "path"="/products/{product_id}/attributes_text/{id}.{_format}",
  333.  *              "identifiers"={"id"},
  334.  *              "input"=ProductAttributeInputText::class,
  335.  *              "output"=ProductAttributeOutputText::class,
  336.  *              "openapi_context"={
  337.  *                  "summary"="Update a product-specific textarea attribute",
  338.  *                  "parameters"={
  339.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  340.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  341.  *                  }
  342.  *              }
  343.  *          },
  344.  *          "product_based_delete_text"={
  345.  *              "method"="DELETE",
  346.  *              "path"="/products/{product_id}/attributes_text/{id}.{_format}",
  347.  *              "identifiers"={"id"},
  348.  *              "input"=ProductAttributeInputText::class,
  349.  *              "output"=ProductAttributeOutputText::class,
  350.  *              "openapi_context"={
  351.  *                  "summary"="Delete a product-specific textarea attribute",
  352.  *                  "parameters"={
  353.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  354.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  355.  *                  }
  356.  *              }
  357.  *          },
  358.  *          "product_based_get_checkbox"={
  359.  *              "method"="GET",
  360.  *              "path"="/products/{product_id}/attributes_checkbox/{id}.{_format}",
  361.  *              "identifiers"={"id"},
  362.  *              "input"=ProductAttributeInputCheckbox::class,
  363.  *              "output"=ProductAttributeOutputCheckbox::class,
  364.  *              "openapi_context"={
  365.  *                  "summary"="Retrieve a product-specific yes/no attribute",
  366.  *                  "parameters"={
  367.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  368.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  369.  *                  }
  370.  *              }
  371.  *          },
  372.  *          "product_based_put_checkbox"={
  373.  *              "method"="PUT",
  374.  *              "path"="/products/{product_id}/attributes_checkbox/{id}.{_format}",
  375.  *              "identifiers"={"id"},
  376.  *              "input"=ProductAttributeInputCheckbox::class,
  377.  *              "output"=ProductAttributeOutputCheckbox::class,
  378.  *              "openapi_context"={
  379.  *                  "summary"="Update a product-specific yes/no attribute",
  380.  *                  "parameters"={
  381.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  382.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  383.  *                  }
  384.  *              }
  385.  *          },
  386.  *          "product_based_delete_checkbox"={
  387.  *              "method"="DELETE",
  388.  *              "path"="/products/{product_id}/attributes_checkbox/{id}.{_format}",
  389.  *              "identifiers"={"id"},
  390.  *              "input"=ProductAttributeInputCheckbox::class,
  391.  *              "output"=ProductAttributeOutputCheckbox::class,
  392.  *              "openapi_context"={
  393.  *                  "summary"="Delete a product-specific yes/no attribute",
  394.  *                  "parameters"={
  395.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  396.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  397.  *                  }
  398.  *              }
  399.  *          },
  400.  *          "product_based_get_select"={
  401.  *              "method"="GET",
  402.  *              "path"="/products/{product_id}/attributes_select/{id}.{_format}",
  403.  *              "identifiers"={"id"},
  404.  *              "input"=ProductAttributeInputSelect::class,
  405.  *              "output"=ProductAttributeOutputSelect::class,
  406.  *              "openapi_context"={
  407.  *                  "summary"="Retrieve a product-specific plain field attribute",
  408.  *                  "parameters"={
  409.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  410.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  411.  *                  }
  412.  *              }
  413.  *          },
  414.  *          "product_based_put_select"={
  415.  *              "method"="PUT",
  416.  *              "path"="/products/{product_id}/attributes_select/{id}.{_format}",
  417.  *              "identifiers"={"id"},
  418.  *              "input"=ProductAttributeInputSelect::class,
  419.  *              "output"=ProductAttributeOutputSelect::class,
  420.  *              "openapi_context"={
  421.  *                  "summary"="Update a product-specific plain field attribute",
  422.  *                  "parameters"={
  423.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  424.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  425.  *                  }
  426.  *              }
  427.  *          },
  428.  *          "product_based_delete_select"={
  429.  *              "method"="DELETE",
  430.  *              "path"="/products/{product_id}/attributes_select/{id}.{_format}",
  431.  *              "identifiers"={"id"},
  432.  *              "input"=ProductAttributeInputSelect::class,
  433.  *              "output"=ProductAttributeOutputSelect::class,
  434.  *              "openapi_context"={
  435.  *                  "summary"="Delete a product-specific plain field attribute",
  436.  *                  "parameters"={
  437.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  438.  *                     {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  439.  *                  }
  440.  *              }
  441.  *          }
  442.  *     },
  443.  *     collectionOperations={
  444.  *          "get_texts"={
  445.  *              "method"="GET",
  446.  *              "path"="/attributes_text.{_format}",
  447.  *              "identifiers"={"id"},
  448.  *              "input"=InputText::class,
  449.  *              "output"=OutputText::class,
  450.  *              "openapi_context"={
  451.  *                  "summary"="Retrieve a list of global textarea attributes",
  452.  *              }
  453.  *          },
  454.  *          "post_text"={
  455.  *              "method"="POST",
  456.  *              "path"="/attributes_text.{_format}",
  457.  *              "identifiers"={"id"},
  458.  *              "input"=InputText::class,
  459.  *              "output"=OutputText::class,
  460.  *              "controller"="xcart.api.attribute.text.controller",
  461.  *              "openapi_context"={
  462.  *                  "summary"="Create a global textarea attribute",
  463.  *              }
  464.  *          },
  465.  *          "get_checkboxes"={
  466.  *              "method"="GET",
  467.  *              "path"="/attributes_checkbox.{_format}",
  468.  *              "identifiers"={"id"},
  469.  *              "input"=InputCheckbox::class,
  470.  *              "output"=OutputCheckbox::class,
  471.  *              "openapi_context"={
  472.  *                  "summary"="Retrieve a list of global yes/no attributes",
  473.  *              }
  474.  *          },
  475.  *          "post_checkbox"={
  476.  *              "method"="POST",
  477.  *              "path"="/attributes_checkbox.{_format}",
  478.  *              "identifiers"={"id"},
  479.  *              "input"=InputCheckbox::class,
  480.  *              "output"=OutputCheckbox::class,
  481.  *              "controller"="xcart.api.attribute.checkbox.controller",
  482.  *              "openapi_context"={
  483.  *                  "summary"="Create a global yes/no attribute",
  484.  *              }
  485.  *          },
  486.  *          "get_selects"={
  487.  *              "method"="GET",
  488.  *              "path"="/attributes_select.{_format}",
  489.  *              "identifiers"={"id"},
  490.  *              "input"=InputSelect::class,
  491.  *              "output"=OutputSelect::class,
  492.  *              "openapi_context"={
  493.  *                  "summary"="Retrieve a list of global plain field attributes",
  494.  *              }
  495.  *          },
  496.  *          "post_select"={
  497.  *              "method"="POST",
  498.  *              "path"="/attributes_select.{_format}",
  499.  *              "identifiers"={"id"},
  500.  *              "input"=InputSelect::class,
  501.  *              "output"=OutputSelect::class,
  502.  *              "controller"="xcart.api.attribute.select.controller",
  503.  *              "openapi_context"={
  504.  *                  "summary"="Create a global plain field attribute",
  505.  *              }
  506.  *          },
  507.  *          "get_hiddens"={
  508.  *              "method"="GET",
  509.  *              "path"="/attributes_hidden.{_format}",
  510.  *              "identifiers"={"id"},
  511.  *              "input"=InputHidden::class,
  512.  *              "output"=OutputHidden::class,
  513.  *              "openapi_context"={
  514.  *                  "summary"="Retrieve a list of global hidden attributes",
  515.  *              }
  516.  *          },
  517.  *          "post_hidden"={
  518.  *              "method"="POST",
  519.  *              "path"="/attributes_hidden.{_format}",
  520.  *              "identifiers"={"id"},
  521.  *              "input"=InputHidden::class,
  522.  *              "output"=OutputHidden::class,
  523.  *              "controller"="xcart.api.attribute.hidden.controller",
  524.  *              "openapi_context"={
  525.  *                  "summary"="Create a global hidden attribute",
  526.  *              }
  527.  *          },
  528.  *          "product_class_based_get_texts"={
  529.  *              "method"="GET",
  530.  *              "path"="/product_classes/{class_id}/attributes_text.{_format}",
  531.  *              "identifiers"={"id"},
  532.  *              "input"=InputText::class,
  533.  *              "output"=OutputText::class,
  534.  *              "openapi_context"={
  535.  *                  "summary"="Retrieve a list of product class textarea attributes",
  536.  *                  "parameters"={
  537.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  538.  *                  }
  539.  *              }
  540.  *          },
  541.  *          "product_class_based_post_text"={
  542.  *              "method"="POST",
  543.  *              "path"="/product_classes/{class_id}/attributes_text.{_format}",
  544.  *              "identifiers"={"id"},
  545.  *              "input"=InputText::class,
  546.  *              "output"=OutputText::class,
  547.  *              "controller"="xcart.api.attribute.text.product_class_based_controller",
  548.  *              "openapi_context"={
  549.  *                  "summary"="Add a textarea attribute to a product class",
  550.  *                  "parameters"={
  551.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  552.  *                  }
  553.  *              }
  554.  *          },
  555.  *          "product_class_based_get_checkboxes"={
  556.  *              "method"="GET",
  557.  *              "path"="/product_classes/{class_id}/attributes_checkbox.{_format}",
  558.  *              "identifiers"={"id"},
  559.  *              "input"=InputCheckbox::class,
  560.  *              "output"=OutputCheckbox::class,
  561.  *              "openapi_context"={
  562.  *                  "summary"="Retrieve a list of product class yes/no attributes",
  563.  *                  "parameters"={
  564.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  565.  *                  }
  566.  *              }
  567.  *          },
  568.  *          "product_class_based_post_checkbox"={
  569.  *              "method"="POST",
  570.  *              "path"="/product_classes/{class_id}/attributes_checkbox.{_format}",
  571.  *              "identifiers"={"id"},
  572.  *              "input"=InputCheckbox::class,
  573.  *              "output"=OutputCheckbox::class,
  574.  *              "controller"="xcart.api.attribute.checkbox.product_class_based_controller",
  575.  *              "openapi_context"={
  576.  *                  "summary"="Add a yes/no attribute to a product class",
  577.  *                  "parameters"={
  578.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  579.  *                  }
  580.  *              }
  581.  *          },
  582.  *          "product_class_based_get_selects"={
  583.  *              "method"="GET",
  584.  *              "path"="/product_classes/{class_id}/attributes_select.{_format}",
  585.  *              "identifiers"={"id"},
  586.  *              "input"=InputSelect::class,
  587.  *              "output"=OutputSelect::class,
  588.  *              "openapi_context"={
  589.  *                  "summary"="Retrieve a list of product class plain field attributes",
  590.  *                  "parameters"={
  591.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  592.  *                  }
  593.  *              }
  594.  *          },
  595.  *          "product_class_based_post_select"={
  596.  *              "method"="POST",
  597.  *              "path"="/product_classes/{class_id}/attributes_select.{_format}",
  598.  *              "identifiers"={"id"},
  599.  *              "input"=InputSelect::class,
  600.  *              "output"=OutputSelect::class,
  601.  *              "controller"="xcart.api.attribute.select.product_class_based_controller",
  602.  *              "openapi_context"={
  603.  *                  "summary"="Add a plain field attribute to a product class",
  604.  *                  "parameters"={
  605.  *                     {"name"="class_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  606.  *                  }
  607.  *              }
  608.  *          },
  609.  *          "product_based_get_texts"={
  610.  *              "method"="GET",
  611.  *              "path"="/products/{product_id}/attributes_text.{_format}",
  612.  *              "identifiers"={"id"},
  613.  *              "input"=ProductAttributeInputText::class,
  614.  *              "output"=ProductAttributeOutputText::class,
  615.  *              "openapi_context"={
  616.  *                  "summary"="Retrieve a list of product-specific textarea attributes",
  617.  *                  "parameters"={
  618.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  619.  *                  }
  620.  *              }
  621.  *          },
  622.  *          "product_based_post_text"={
  623.  *              "method"="POST",
  624.  *              "path"="/products/{product_id}/attributes_text.{_format}",
  625.  *              "identifiers"={"id"},
  626.  *              "input"=ProductAttributeInputText::class,
  627.  *              "output"=ProductAttributeOutputText::class,
  628.  *              "controller"="xcart.api.attribute.text.product_based_controller",
  629.  *              "openapi_context"={
  630.  *                  "summary"="Add a textarea attribute to a product",
  631.  *                  "parameters"={
  632.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  633.  *                  }
  634.  *              }
  635.  *          },
  636.  *          "product_based_get_checkboxes"={
  637.  *              "method"="GET",
  638.  *              "path"="/products/{product_id}/attributes_checkbox.{_format}",
  639.  *              "identifiers"={"id"},
  640.  *              "input"=ProductAttributeInputCheckbox::class,
  641.  *              "output"=ProductAttributeOutputCheckbox::class,
  642.  *              "openapi_context"={
  643.  *                  "summary"="Retrieve a list of product-specific yes/no attributes",
  644.  *                  "parameters"={
  645.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  646.  *                  }
  647.  *              }
  648.  *          },
  649.  *          "product_based_post_checkbox"={
  650.  *              "method"="POST",
  651.  *              "path"="/products/{product_id}/attributes_checkbox.{_format}",
  652.  *              "identifiers"={"id"},
  653.  *              "input"=ProductAttributeInputCheckbox::class,
  654.  *              "output"=ProductAttributeOutputCheckbox::class,
  655.  *              "controller"="xcart.api.attribute.checkbox.product_based_controller",
  656.  *              "openapi_context"={
  657.  *                  "summary"="Add a yes/no attribute to a product",
  658.  *                  "parameters"={
  659.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  660.  *                  }
  661.  *              }
  662.  *          },
  663.  *          "product_based_get_selects"={
  664.  *              "method"="GET",
  665.  *              "path"="/products/{product_id}/attributes_select.{_format}",
  666.  *              "identifiers"={"id"},
  667.  *              "input"=ProductAttributeInputSelect::class,
  668.  *              "output"=ProductAttributeOutputSelect::class,
  669.  *              "openapi_context"={
  670.  *                  "summary"="Retrieve a list of product-specific plain field attributes",
  671.  *                  "parameters"={
  672.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  673.  *                  }
  674.  *              }
  675.  *          },
  676.  *          "product_based_post_select"={
  677.  *              "method"="POST",
  678.  *              "path"="/products/{product_id}/attributes_select.{_format}",
  679.  *              "identifiers"={"id"},
  680.  *              "input"=ProductAttributeInputSelect::class,
  681.  *              "output"=ProductAttributeOutputSelect::class,
  682.  *              "controller"="xcart.api.attribute.select.product_based_controller",
  683.  *              "openapi_context"={
  684.  *                  "summary"="Add a plain field attribute to a product",
  685.  *                  "parameters"={
  686.  *                     {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  687.  *                  }
  688.  *              }
  689.  *          }
  690.  *     }
  691.  * )
  692.  */
  693. class Attribute extends \XLite\Model\Base\I18n
  694. {
  695.     use ExecuteCachedTrait;
  696.     /*
  697.      * Attribute types
  698.      */
  699.     public const TYPE_TEXT     'T';
  700.     public const TYPE_CHECKBOX 'C';
  701.     public const TYPE_SELECT   'S';
  702.     public const TYPE_HIDDEN   'H';
  703.     /*
  704.      * Add to new products or class’s assigns automatically with select value
  705.      */
  706.     public const ADD_TO_NEW_YES    'Y'// 'Yes'
  707.     public const ADD_TO_NEW_NO     'N'// 'NO'
  708.     public const ADD_TO_NEW_YES_NO 'B'// 'YES/NO' (BOTH)
  709.     /*
  710.      * Attribute delimiter
  711.      */
  712.     public const DELIMITER ', ';
  713.     /*
  714.      * Display modes
  715.      */
  716.     public const SELECT_BOX_MODE 'S';
  717.     public const SPECIFICATION_MODE 'P';
  718.     public const BLOCKS_MODE     'B';
  719.     /**
  720.      * @var int
  721.      *
  722.      * @ORM\Id
  723.      * @ORM\GeneratedValue (strategy="AUTO")
  724.      * @ORM\Column (type="integer", options={ "unsigned": true })
  725.      */
  726.     protected $id;
  727.     /**
  728.      * @var int
  729.      *
  730.      * @ORM\Column (type="integer")
  731.      */
  732.     protected $position 0;
  733.     /**
  734.      * Is attribute shown above the price
  735.      *
  736.      * @var bool
  737.      *
  738.      * @ORM\Column (type="boolean", options={"default":"0"})
  739.      */
  740.     protected $displayAbove false;
  741.     /**
  742.      * @var int
  743.      *
  744.      * @ORM\Column (type="integer", length=1)
  745.      */
  746.     protected $decimals 0;
  747.     /**
  748.      * @var \XLite\Model\ProductClass
  749.      *
  750.      * @ORM\ManyToOne (targetEntity="XLite\Model\ProductClass", inversedBy="attributes")
  751.      * @ORM\JoinColumn (name="product_class_id", referencedColumnName="id", onDelete="CASCADE")
  752.      */
  753.     protected $productClass;
  754.     /**
  755.      * @var \XLite\Model\AttributeGroup
  756.      *
  757.      * @ORM\ManyToOne (targetEntity="XLite\Model\AttributeGroup", inversedBy="attributes")
  758.      * @ORM\JoinColumn (name="attribute_group_id", referencedColumnName="id")
  759.      */
  760.     protected $attributeGroup;
  761.     /**
  762.      * @var \Doctrine\Common\Collections\Collection
  763.      *
  764.      * @ORM\OneToMany (targetEntity="XLite\Model\AttributeOption", mappedBy="attribute", cascade={"all"})
  765.      */
  766.     protected $attribute_options;
  767.     /**
  768.      * @var \XLite\Model\Product
  769.      *
  770.      * @ORM\ManyToOne (targetEntity="XLite\Model\Product", inversedBy="attributes")
  771.      * @ORM\JoinColumn (name="product_id", referencedColumnName="product_id", onDelete="CASCADE")
  772.      */
  773.     protected $product;
  774.     /**
  775.      * Option type
  776.      *
  777.      * @var string
  778.      *
  779.      * @ORM\Column (type="string", options={"fixed": true}, length=1)
  780.      */
  781.     protected $type self::TYPE_SELECT;
  782.     /**
  783.      * @var string
  784.      *
  785.      * @ORM\Column (type="string", options={"fixed": true}, length=1)
  786.      */
  787.     protected $displayMode '';
  788.     /**
  789.      * Add to new products or class’s assigns automatically
  790.      *
  791.      * @var bool
  792.      *
  793.      * @ORM\Column (type="string", options={"fixed": true}, length=1)
  794.      */
  795.     protected $addToNew '';
  796.     /**
  797.      * @var \Doctrine\Common\Collections\Collection
  798.      *
  799.      * @ORM\OneToMany (targetEntity="XLite\Model\AttributeProperty", mappedBy="attribute")
  800.      */
  801.     protected $attribute_properties;
  802.     /**
  803.      * @var \Doctrine\Common\Collections\Collection
  804.      *
  805.      * @ORM\OneToMany (targetEntity="XLite\Model\AttributeTranslation", mappedBy="owner", cascade={"all"})
  806.      */
  807.     protected $translations;
  808.     private ?string $precalculatedName null;
  809.     private ?int $precalculatedAddToNewOptionsCount null;
  810.     /**
  811.      * Return name of widget class
  812.      *
  813.      * @param string $type      Attribute type
  814.      * @param string $interface Interface (Admin | Customer) OPTIONAL
  815.      *
  816.      * @return string
  817.      */
  818.     public static function getWidgetClass($type$interface null)
  819.     {
  820.         if ($interface === null) {
  821.             $interface \XLite::isAdminZone() ? 'Admin' 'Customer';
  822.         }
  823.         return '\XLite\View\Product\AttributeValue\\'
  824.             $interface
  825.             '\\'
  826.             . static::getTypes($typetrue);
  827.     }
  828.     /**
  829.      * Return name of value class
  830.      *
  831.      * @param string $type Type
  832.      *
  833.      * @return string
  834.      */
  835.     public static function getAttributeValueClass($type)
  836.     {
  837.         return '\XLite\Model\AttributeValue\AttributeValue'
  838.             . static::getTypes($typetrue);
  839.     }
  840.     /**
  841.      * Constructor
  842.      *
  843.      * @param array $data Entity properties OPTIONAL
  844.      */
  845.     public function __construct(array $data = [])
  846.     {
  847.         $this->attribute_options = new \Doctrine\Common\Collections\ArrayCollection();
  848.         parent::__construct($data);
  849.     }
  850.     /**
  851.      * Return number of products associated with this attribute
  852.      *
  853.      * @return integer
  854.      */
  855.     public function getProductsCount()
  856.     {
  857.         return $this->getClass()->getProductsCount();
  858.     }
  859.     /**
  860.      * Return list of types or type
  861.      *
  862.      * @param string  $type              Type OPTIONAL
  863.      * @param boolean $returnServiceType Return service type OPTIONAL
  864.      *
  865.      * @return array | string
  866.      */
  867.     public static function getTypes($type null$returnServiceType false)
  868.     {
  869.         $list = [
  870.             static::TYPE_SELECT   => static::t('Plain field'),
  871.             static::TYPE_TEXT     => static::t('Textarea'),
  872.             static::TYPE_CHECKBOX => static::t('Yes/No'),
  873.             static::TYPE_HIDDEN   => static::t('Hidden field'),
  874.         ];
  875.         $listServiceTypes = [
  876.             static::TYPE_SELECT   => 'Select',
  877.             static::TYPE_TEXT     => 'Text',
  878.             static::TYPE_CHECKBOX => 'Checkbox',
  879.             static::TYPE_HIDDEN   => 'Hidden',
  880.         ];
  881.         $list $returnServiceType $listServiceTypes $list;
  882.         return $type !== null
  883.             ? ($list[$type] ?? null)
  884.             : $list;
  885.     }
  886.     /**
  887.      * Return list of 'addToNew' types
  888.      *
  889.      * @return array
  890.      */
  891.     public static function getAddToNewTypes()
  892.     {
  893.         return [
  894.             static::ADD_TO_NEW_YES,
  895.             static::ADD_TO_NEW_NO,
  896.             static::ADD_TO_NEW_YES_NO,
  897.         ];
  898.     }
  899.     /**
  900.      * Return values associated with this attribute
  901.      *
  902.      * @return list<\Xlite\Model\AttributeValue\AAttributeValue>
  903.      */
  904.     public function getAttributeValues()
  905.     {
  906.         $cnd = new \XLite\Core\CommonCell();
  907.         $cnd->attribute $this;
  908.         return Database::getRepo(static::getAttributeValueClass($this->getType()))
  909.             ->search($cnd);
  910.     }
  911.     /**
  912.      * Return number of values associated with this attribute
  913.      *
  914.      * @return integer
  915.      */
  916.     public function getAttributeValuesCount()
  917.     {
  918.         $cnd = new \XLite\Core\CommonCell();
  919.         $cnd->attribute $this;
  920.         return Database::getRepo(static::getAttributeValueClass($this->getType()))
  921.             ->search($cndtrue);
  922.     }
  923.     /**
  924.      * Set 'addToNew' value
  925.      *
  926.      * @param string|array $value Value
  927.      *
  928.      * @return void
  929.      */
  930.     public function setAddToNew($value)
  931.     {
  932.         if (
  933.             is_array($value)
  934.             && $this->getType() === static::TYPE_CHECKBOX
  935.         ) {
  936.             if (count($value) === 2) {
  937.                 $value = static::ADD_TO_NEW_YES_NO;
  938.             } elseif (count($value) === 1) {
  939.                 $value array_shift($value) ? static::ADD_TO_NEW_YES : static::ADD_TO_NEW_NO;
  940.             }
  941.         }
  942.         $this->addToNew in_array($value, static::getAddToNewTypes()) ? $value '';
  943.     }
  944.     /**
  945.      * Get 'addToNew' value
  946.      *
  947.      * @return array
  948.      */
  949.     public function getAddToNew()
  950.     {
  951.         $value null;
  952.         if ($this->getType() === static::TYPE_CHECKBOX) {
  953.             switch ($this->addToNew) {
  954.                 case static::ADD_TO_NEW_YES:
  955.                     $value = [1];
  956.                     break;
  957.                 case static::ADD_TO_NEW_NO:
  958.                     $value = [0];
  959.                     break;
  960.                 case static::ADD_TO_NEW_YES_NO:
  961.                     $value = [01];
  962.                     break;
  963.                 default:
  964.             }
  965.         }
  966.         return $value;
  967.     }
  968.     /**
  969.      * Set type
  970.      *
  971.      * @param string $type Type
  972.      *
  973.      * @return void
  974.      */
  975.     public function setType($type)
  976.     {
  977.         $types = static::getTypes();
  978.         if (isset($types[$type])) {
  979.             if (
  980.                 $this->type
  981.                 && $type != $this->type
  982.                 && $this->getId()
  983.             ) {
  984.                 foreach ($this->getAttributeOptions() as $option) {
  985.                     Database::getEM()->remove($option);
  986.                 }
  987.                 foreach ($this->getAttributeValues() as $value) {
  988.                     Database::getEM()->remove($value);
  989.                 }
  990.             }
  991.             $this->type $type;
  992.         }
  993.     }
  994.     /**
  995.      * Return product property (return new property if property does not exist)
  996.      *
  997.      * @param \XLite\Model\Product $product Product OPTIONAL
  998.      *
  999.      * @return \XLite\Model\AttributeProperty
  1000.      */
  1001.     public function getProperty($product)
  1002.     {
  1003.         return $this->executeCachedRuntime(function () use ($product) {
  1004.             $property null;
  1005.             if ($product->getProductId()) {
  1006.                 $product Database::getRepo(\XLite\Model\Product::class)?->find($product->getProductId());
  1007.                 $property Database::getRepo(\XLite\Model\AttributeProperty::class)?->findOneBy([
  1008.                     'product' => $product,
  1009.                     'attribute'  => $this,
  1010.                 ]);
  1011.                 if ($property === null) {
  1012.                     $property $this->getNewProperty($product);
  1013.                 }
  1014.             }
  1015.             return $property;
  1016.         }, ['getProperty'$this->getId(), $product->getProductId()]);
  1017.     }
  1018.     /**
  1019.      * Return new product property
  1020.      *
  1021.      * @param \XLite\Model\Product $product Product OPTIONAL
  1022.      *
  1023.      * @return \XLite\Model\AttributeProperty
  1024.      */
  1025.     protected function getNewProperty($product)
  1026.     {
  1027.         $result = new \XLite\Model\AttributeProperty();
  1028.         $result->setAttribute($this);
  1029.         $result->setProduct($product);
  1030.         $result->setDisplayAbove($this->getDisplayAbove());
  1031.         $this->addAttributeProperty($result);
  1032.         Database::getEM()->persist($result);
  1033.         return $result;
  1034.     }
  1035.     /**
  1036.      * Returns position
  1037.      *
  1038.      * @param \XLite\Model\Product $product Product OPTIONAL
  1039.      *
  1040.      * @return integer
  1041.      */
  1042.     public function getPosition($product null)
  1043.     {
  1044.         if ($product) {
  1045.             $result $this->getProperty($product);
  1046.             $result $result $result->getPosition() : 0;
  1047.         } else {
  1048.             $result $this->position;
  1049.         }
  1050.         return $result;
  1051.     }
  1052.     /**
  1053.      * Set the position
  1054.      *
  1055.      * @param integer|array $value
  1056.      *
  1057.      * @return void
  1058.      */
  1059.     public function setPosition($value)
  1060.     {
  1061.         if (is_array($value)) {
  1062.             $property $this->getProperty($value['product']);
  1063.             $property->setPosition($value['position']);
  1064.         } else {
  1065.             $this->position $value;
  1066.         }
  1067.     }
  1068.     /**
  1069.      * @param \XLite\Model\Product $product Product OPTIONAL
  1070.      *
  1071.      * @return integer
  1072.      */
  1073.     public function getDisplayAbove($product null)
  1074.     {
  1075.         if ($product) {
  1076.             $result $this->getProperty($product);
  1077.             $result $result $result->getDisplayAbove() : $this->displayAbove;
  1078.         } else {
  1079.             $result $this->displayAbove;
  1080.         }
  1081.         return $result;
  1082.     }
  1083.     /**
  1084.      * @param boolean|array $value
  1085.      *
  1086.      * @return void
  1087.      */
  1088.     public function setDisplayAbove($value)
  1089.     {
  1090.         if (is_array($value)) {
  1091.             $property $this->getProperty($value['product']);
  1092.             $property->setDisplayAbove($value['displayAbove']);
  1093.         } else {
  1094.             $this->displayAbove $value;
  1095.         }
  1096.     }
  1097.     /**
  1098.      * Add to new product
  1099.      *
  1100.      * @param \XLite\Model\Product $product Product
  1101.      *
  1102.      * @return void
  1103.      */
  1104.     public function addToNewProduct(\XLite\Model\Product $product)
  1105.     {
  1106.         $displayAbove $this->getDisplayAbove();
  1107.         if ($this->getAddToNew()) {
  1108.             $displayAbove count($this->getAddToNew()) > ?: $displayAbove;
  1109.             foreach ($this->getAddToNew() as $value) {
  1110.                 $av $this->createAttributeValue($product);
  1111.                 if ($av) {
  1112.                     $av->setValue($value);
  1113.                 }
  1114.             }
  1115.         } elseif ($this->getType() === static::TYPE_SELECT) {
  1116.             $attributeOptions Database::getRepo(AttributeOption::class)->findBy(
  1117.                 [
  1118.                     'attribute' => $this,
  1119.                     'addToNew'  => true,
  1120.                 ],
  1121.                 ['position' => 'ASC']
  1122.             );
  1123.             $displayAbove count($attributeOptions) > ?: $displayAbove;
  1124.             foreach ($attributeOptions as $attributeOption) {
  1125.                 $av $this->createAttributeValue($product);
  1126.                 if ($av) {
  1127.                     $av->setAttributeOption($attributeOption);
  1128.                     $av->setPosition($attributeOption->getPosition());
  1129.                 }
  1130.             }
  1131.         } elseif ($this->getType() === static::TYPE_TEXT) {
  1132.             $av $this->createAttributeValue($product);
  1133.             if ($av) {
  1134.                 $av->setEditable(false);
  1135.                 $av->setValue('');
  1136.             }
  1137.         } elseif ($this->getType() === static::TYPE_HIDDEN) {
  1138.             $attributeOption Database::getRepo(AttributeOption::class)->findOneBy(
  1139.                 [
  1140.                     'attribute' => $this,
  1141.                     'addToNew'  => true,
  1142.                 ]
  1143.             );
  1144.             if ($attributeOption) {
  1145.                 $av $this->createAttributeValue($product);
  1146.                 if ($av) {
  1147.                     $av->setAttributeOption($attributeOption);
  1148.                 }
  1149.             }
  1150.         }
  1151.         $this->setDisplayAbove(
  1152.             [
  1153.                 'product' => $product,
  1154.                 'displayAbove' => $displayAbove,
  1155.             ]
  1156.         );
  1157.     }
  1158.     /**
  1159.      * Apply changes
  1160.      *
  1161.      * @param \XLite\Model\Product $product Product
  1162.      * @param mixed                $changes Changes
  1163.      *
  1164.      * @return void
  1165.      */
  1166.     public function applyChanges(\XLite\Model\Product $product$changes)
  1167.     {
  1168.         if (
  1169.             (
  1170.                 !$this->getProductClass()
  1171.                 && !$this->getProduct()
  1172.             )
  1173.             || (
  1174.                 $this->getProductClass()
  1175.                 && $product->getProductClass()
  1176.                 && $this->getProductClass()->getId() == $product->getProductClass()->getId()
  1177.             )
  1178.             || ($this->getProduct()
  1179.                 && $this->getProduct()->getId() == $product->getId()
  1180.             )
  1181.         ) {
  1182.             $class = static::getAttributeValueClass($this->getType());
  1183.             $repo Database::getRepo($class);
  1184.             switch ($this->getType()) {
  1185.                 case static::TYPE_TEXT:
  1186.                     $this->setAttributeValue($product$changes);
  1187.                     break;
  1188.                 case static::TYPE_CHECKBOX:
  1189.                 case static::TYPE_SELECT:
  1190.                     foreach ($repo->findBy(['product' => $product'attribute' => $this]) as $av) {
  1191.                         $uniq $this->getType() === static::TYPE_CHECKBOX
  1192.                             $av->getValue()
  1193.                             : $av->getAttributeOption()->getId();
  1194.                         if (in_array($uniq$changes['deleted'])) {
  1195.                             $repo->delete($avfalse);
  1196.                         } elseif (
  1197.                             isset($changes['changed'][$uniq])
  1198.                             || isset($changes['added'][$uniq])
  1199.                         ) {
  1200.                             $data $changes['changed'][$uniq] ?? $changes['added'][$uniq];
  1201.                             if (
  1202.                                 isset($data['defaultValue'])
  1203.                                 && $data['defaultValue']
  1204.                                 && !$av->getDefaultValue()
  1205.                             ) {
  1206.                                 $pr $repo->findOneBy(
  1207.                                     [
  1208.                                         'product'      => $product,
  1209.                                         'attribute'    => $this,
  1210.                                         'defaultValue' => true,
  1211.                                     ]
  1212.                                 );
  1213.                                 if ($pr) {
  1214.                                     $pr->setDefaultValue(false);
  1215.                                 }
  1216.                             }
  1217.                             $repo->update($av$data);
  1218.                             if (isset($changes['added'][$uniq])) {
  1219.                                 unset($changes['added'][$uniq]);
  1220.                             }
  1221.                         }
  1222.                     }
  1223.                     if ($changes['added']) {
  1224.                         foreach ($changes['added'] as $uniq => $data) {
  1225.                             if (
  1226.                                 isset($data['defaultValue'])
  1227.                                 && $data['defaultValue']
  1228.                             ) {
  1229.                                 $pr $repo->findOneBy(
  1230.                                     [
  1231.                                         'product'      => $product,
  1232.                                         'attribute'    => $this,
  1233.                                         'defaultValue' => true,
  1234.                                     ]
  1235.                                 );
  1236.                                 if ($pr) {
  1237.                                     $pr->setDefaultValue(false);
  1238.                                 }
  1239.                             }
  1240.                             $av $this->createAttributeValue($product);
  1241.                             if ($av) {
  1242.                                 if ($this->getType() === static::TYPE_CHECKBOX) {
  1243.                                     $av->setValue($uniq);
  1244.                                 } else {
  1245.                                     $av->setAttributeOption(
  1246.                                         Database::getRepo(AttributeOption::class)->find($uniq)
  1247.                                     );
  1248.                                 }
  1249.                                 $repo->update($av$data);
  1250.                             }
  1251.                         }
  1252.                     }
  1253.                     break;
  1254.                 default:
  1255.             }
  1256.             Database::getEM()->flush();
  1257.         }
  1258.     }
  1259.     /**
  1260.      * Set attribute value
  1261.      *
  1262.      * @param \XLite\Model\Product $product Product
  1263.      * @param mixed                $data    Value
  1264.      *
  1265.      * @return void
  1266.      */
  1267.     public function setAttributeValue(\XLite\Model\Product $product$databool $flush true)
  1268.     {
  1269.         $repo Database::getRepo(
  1270.             static::getAttributeValueClass($this->getType())
  1271.         );
  1272.         $method $this->defineSetAttributeValueMethodName($data);
  1273.         $this->$method($repo$product$data$flush);
  1274.     }
  1275.     /**
  1276.      * Get attribute value
  1277.      *
  1278.      * @param \XLite\Model\Product $product  Product
  1279.      * @param boolean              $asString As string flag OPTIONAL
  1280.      *
  1281.      * @return mixed
  1282.      */
  1283.     public function getAttributeValue(\XLite\Model\Product $product$asString false)
  1284.     {
  1285.         return Database::getRepo(static::class)?->getAttributeValue($product$this->getType(), $this->getId(), $asString);
  1286.     }
  1287.     /**
  1288.      * Get attribute value
  1289.      *
  1290.      * @param \XLite\Model\Product $product Product
  1291.      *
  1292.      * @return \XLite\Model\AttributeValue\AAttributeValue
  1293.      */
  1294.     public function getDefaultAttributeValue(\XLite\Model\Product $product)
  1295.     {
  1296.         $repo Database::getRepo(static::getAttributeValueClass($this->getType()));
  1297.         $attributeValue $repo->findOneBy(['product' => $product'attribute' => $this'defaultValue' => true]);
  1298.         if (!$attributeValue) {
  1299.             $attributeValue $repo->findDefaultAttributeValue(['product' => $product'attribute' => $this]);
  1300.         }
  1301.         return $attributeValue;
  1302.     }
  1303.     /**
  1304.      * This attribute is multiple or not flag
  1305.      *
  1306.      * @param \XLite\Model\Product $product Product
  1307.      *
  1308.      * @return boolean
  1309.      */
  1310.     public function isMultiple(\XLite\Model\Product $product)
  1311.     {
  1312.         $repo Database::getRepo(static::getAttributeValueClass($this->getType()));
  1313.         return (!$this->getProduct() || $this->getProduct()->getId() == $product->getId())
  1314.             && (!$this->getProductClass()
  1315.                 || ($product->getProductClass()
  1316.                     && $this->getProductClass()->getId() == $product->getProductClass()->getId()
  1317.                 )
  1318.             )
  1319.             && count($repo->findBy(['product' => $product'attribute' => $this]));
  1320.     }
  1321.     /**
  1322.      * This attribute is hidden or not flag
  1323.      *
  1324.      * @return bool
  1325.      */
  1326.     public function isHidden()
  1327.     {
  1328.         return $this->getType() === static::TYPE_HIDDEN;
  1329.     }
  1330.     /**
  1331.      * Create attribute value
  1332.      *
  1333.      * @param \XLite\Model\Product $product Product
  1334.      *
  1335.      * @return mixed
  1336.      */
  1337.     protected function createAttributeValue(\XLite\Model\Product $product)
  1338.     {
  1339.         $class = static::getAttributeValueClass($this->getType());
  1340.         $attributeValue = new $class();
  1341.         $attributeValue->setProduct($product);
  1342.         $attributeValue->setAttribute($this);
  1343.         Database::getEM()->persist($attributeValue);
  1344.         return $attributeValue;
  1345.     }
  1346.     /**
  1347.      * Create attribute option
  1348.      *
  1349.      * @param string $value Option name
  1350.      *
  1351.      * @return AttributeOption
  1352.      */
  1353.     protected function createAttributeOption($value)
  1354.     {
  1355.         $attributeOption = new AttributeOption();
  1356.         $attributeOption->setAttribute($this);
  1357.         $attributeOption->setName($value);
  1358.         Database::getEM()->persist($attributeOption);
  1359.         return $attributeOption;
  1360.     }
  1361.     // {{{ Set attribute value
  1362.     /**
  1363.      * Define method name for 'setAttributeValue' operation
  1364.      *
  1365.      * @param mixed $data Data
  1366.      *
  1367.      * @return string
  1368.      */
  1369.     protected function defineSetAttributeValueMethodName($data)
  1370.     {
  1371.         if ($this->getType() === static::TYPE_SELECT) {
  1372.             $result 'setAttributeValueSelect';
  1373.         } elseif ($this->getType() === static::TYPE_CHECKBOX && isset($data['multiple']) && $data['multiple']) {
  1374.             $result 'setAttributeValueCheckbox';
  1375.         } elseif ($this->getType() === static::TYPE_HIDDEN) {
  1376.             $result 'setAttributeValueHidden';
  1377.         } else {
  1378.             $result 'setAttributeValueDefault';
  1379.         }
  1380.         return $result;
  1381.     }
  1382.     /**
  1383.      * Set attribute value (select)
  1384.      *
  1385.      * @param \XLite\Model\Repo\ARepo $repo    Repository
  1386.      * @param \XLite\Model\Product    $product Product
  1387.      * @param array                   $data    Data
  1388.      *
  1389.      * @return void
  1390.      */
  1391.     protected function setAttributeValueSelect(
  1392.         \XLite\Model\Repo\ARepo $repo,
  1393.         \XLite\Model\Product $product,
  1394.         array $data,
  1395.         bool $flush true
  1396.     ) {
  1397.         $ids = [];
  1398.         $values $data['value'] ?? [];
  1399.         krsort($values);
  1400.         foreach ($values as $id => $value) {
  1401.             $value trim($value);
  1402.             if (strlen($value) > && is_int($id)) {
  1403.                 if (!isset($data['deleteValue'][$id])) {
  1404.                     [$avId] = $this->setAttributeValueSelectItem($repo$product$data$id$value$flush);
  1405.                     $ids[$avId] = $avId;
  1406.                 }
  1407.                 if (!isset($data['multiple'])) {
  1408.                     break;
  1409.                 }
  1410.             }
  1411.         }
  1412.         // Make a performance curtsy, especially to import.
  1413.         // We don't need to do anything with EM if the collection hasn't been initialized (fetched from DB).
  1414.         $isCollectionInitialized $product
  1415.             && $product->getAttributeValueS() instanceof PersistentCollection
  1416.             && $product->getAttributeValueS()->isInitialized();
  1417.         foreach ($repo->findBy(['product' => $product'attribute' => $this]) as $data) {
  1418.             if ($data->getId() && !isset($ids[$data->getId()])) {
  1419.                 $repo->delete($datafalse);
  1420.                 if ($isCollectionInitialized) {
  1421.                     // We have to delete child entities in EM otherwise it will be cascadePersisted again (unmark2delete)
  1422.                     // for ex. here \XLite\Logic\Import\Processor\AProcessor::importData()->\XLite\Core\Database::getEM()->persist($model); XCB-2770
  1423.                     // according to doctrine doc https://www.doctrine-project.org/projects/doctrine-orm/en/2.17/reference/working-with-objects.html#persisting-entities
  1424.                     $product->getAttributeValueS()->removeElement($data);
  1425.                 }
  1426.             }
  1427.         }
  1428.     }
  1429.     protected function setAttributeValueSelectItem(
  1430.         \XLite\Model\Repo\ARepo $repo,
  1431.         \XLite\Model\Product $product,
  1432.         array $data,
  1433.         $id,
  1434.         $value,
  1435.         bool $flush true
  1436.     ) {
  1437.         static $runtimeCacheAttributeValues = [];
  1438.         static $runtimeCacheAttributeOptions = [];
  1439.         $result = [nullnullnull];
  1440.         $attributeValue $this->getAttributeOptionByValue($repo$data$id$value);
  1441.         $attributeOption $this->getAttributeOption($value$attributeValue$runtimeCacheAttributeOptions);
  1442.         if (!$attributeValue) {
  1443.             $attributeValue $this->findOrCreateAttributeValue($repo$attributeOption$product$id$data$runtimeCacheAttributeValues);
  1444.         }
  1445.         if ($attributeValue) {
  1446.             $this->processAttributeValue($attributeValue$attributeOption$data$id);
  1447.             if ($flush) {
  1448.                 Database::getEM()->flush();
  1449.             }
  1450.             $result = [
  1451.                 $attributeValue->getId(),
  1452.                 $attributeValue,
  1453.                 $attributeOption,
  1454.             ];
  1455.         }
  1456.         return $result;
  1457.     }
  1458.     protected function getAttributeOptionByValue(Arepo $repo, array $data, ?int $id, ?string $value): ?AAttributeValue
  1459.     {
  1460.         if ($id &&  !isset($data['ignoreIds']) && $this->getProduct()) {
  1461.             $attributeValue $repo->find($id);
  1462.             if ($attributeValue) {
  1463.                 $attributeOption $attributeValue->getAttributeOption();
  1464.                 $attributeOption->setName($value);
  1465.                 return $attributeValue;
  1466.             }
  1467.         }
  1468.         return null;
  1469.     }
  1470.     protected function getAttributeOption(
  1471.         ?string $value,
  1472.         ?AAttributeValue $attributeValue,
  1473.         array &$attributeOptionsRuntimeCache
  1474.     ): AttributeOption {
  1475.         $attributeOptionKey spl_object_id($this) . $value;
  1476.         if ($attributeOption $attributeOptionsRuntimeCache[$attributeOptionKey] ?? null) {
  1477.             return $attributeOption;
  1478.         }
  1479.         if ($attributeValue) {
  1480.             return $attributeValue->getAttributeOption();
  1481.         }
  1482.         $attributeOption Database::getRepo(AttributeOption::class)
  1483.             ->findOneByNameAndAttribute($value$this);
  1484.         if ($attributeOption) {
  1485.             return $attributeOption;
  1486.         }
  1487.         $attributeOption $this->createAttributeOption($value);
  1488.         $attributeOptionsRuntimeCache[$attributeOptionKey] = $attributeOption;
  1489.         return $attributeOption;
  1490.     }
  1491.     protected function findOrCreateAttributeValue(
  1492.         ARepo $repo,
  1493.         AttributeOption $attributeOption,
  1494.         Product $product,
  1495.         ?int $id,
  1496.         array $data,
  1497.         array &$runtimeCacheAttributeValues = []
  1498.     ): AAttributeValue {
  1499.         $attributeValueCacheKey spl_object_id($product) . $attributeOption?->getId();
  1500.         $attributeValue $repo->findOneBy([
  1501.             'attribute_option' => $attributeOption,
  1502.             'product' => $product,
  1503.         ]);
  1504.         if (!$attributeValue && $id && !isset($data['ignoreIds'])) {
  1505.             $attributeValue $repo->find($id);
  1506.         } elseif (isset($runtimeCacheAttributeValues[$attributeValueCacheKey]) && $attributeOption?->getId()) {
  1507.             $attributeValue $runtimeCacheAttributeValues[$attributeValueCacheKey];
  1508.         }
  1509.         if (!$attributeValue) {
  1510.             $attributeValue $this->createAttributeValue($product);
  1511.             $this->setAttributeValuePosition($attributeValue$product);
  1512.             $product->addAttributeValueS($attributeValue);
  1513.             $runtimeCacheAttributeValues[$attributeValueCacheKey] = $attributeValue;
  1514.         }
  1515.         return $attributeValue;
  1516.     }
  1517.     protected function setAttributeValuePosition(AAttributeValue $attributeValueProduct $product): void
  1518.     {
  1519.         $attributeValue->setPosition(
  1520.             array_reduce(
  1521.                 $product->getAttributeValueS()->toArray(),
  1522.                 function ($carryAttributeValueSelect $item) {
  1523.                     return $item->getAttribute() === $this max($carry$item->getPosition()) : $carry;
  1524.                 },
  1525.                 0
  1526.             ) + 10
  1527.         );
  1528.     }
  1529.     protected function processAttributeValue($attributeValue$attributeOption$data$id)
  1530.     {
  1531.         $attributeValue->setAttributeOption($attributeOption);
  1532.         $attributeValue->setDefaultValue(isset($data['default'][$id]));
  1533.         foreach ($attributeValue::getModifiers() as $modifier => $options) {
  1534.             if (isset($data[$modifier][$id])) {
  1535.                 $attributeValue->setModifier($data[$modifier][$id], $modifier);
  1536.             }
  1537.         }
  1538.     }
  1539.     /**
  1540.      * Set attribute value (checkbox)
  1541.      *
  1542.      * @param \XLite\Model\Repo\ARepo $repo    Repository
  1543.      * @param \XLite\Model\Product    $product Product
  1544.      * @param array                   $data    Data
  1545.      *
  1546.      * @return void
  1547.      */
  1548.     protected function setAttributeValueCheckbox(
  1549.         \XLite\Model\Repo\ARepo $repo,
  1550.         \XLite\Model\Product $product,
  1551.         array $data,
  1552.         bool $flush true
  1553.     ) {
  1554.         foreach ([truefalse] as $value) {
  1555.             $this->setAttributeValueCheckboxItem($repo$product$data$value);
  1556.         }
  1557.     }
  1558.     /**
  1559.      * Set attribute value (checkbox item)
  1560.      *
  1561.      * @param \XLite\Model\Repo\ARepo $repo    Repository
  1562.      * @param \XLite\Model\Product    $product Product
  1563.      * @param array                   $data    Data
  1564.      * @param boolean|int             $value   Item value
  1565.      *
  1566.      * @return \XLite\Model\AttributeValue\AttributeValueCheckbox
  1567.      */
  1568.     protected function setAttributeValueCheckboxItem(
  1569.         \XLite\Model\Repo\ARepo $repo,
  1570.         \XLite\Model\Product $product,
  1571.         array $data,
  1572.         $value
  1573.     ) {
  1574.         $attributeValue $repo->findOneBy(
  1575.             [
  1576.                 'product'   => $product,
  1577.                 'attribute' => $this,
  1578.                 'value'     => $value,
  1579.             ]
  1580.         );
  1581.         if (!$attributeValue) {
  1582.             $attributeValue $this->createAttributeValue($product);
  1583.             $attributeValue->setValue($value);
  1584.         }
  1585.         $value = (int) $value;
  1586.         $attributeValue->setDefaultValue(isset($data['default'][$value]));
  1587.         foreach ($attributeValue::getModifiers() as $modifier => $options) {
  1588.             if (isset($data[$modifier]) && isset($data[$modifier][$value])) {
  1589.                 $attributeValue->setModifier($data[$modifier][$value], $modifier);
  1590.             }
  1591.         }
  1592.         return $attributeValue;
  1593.     }
  1594.     /**
  1595.      * Set attribute value (hidden)
  1596.      *
  1597.      * @param \XLite\Model\Repo\ARepo $repo    Repository
  1598.      * @param \XLite\Model\Product    $product Product
  1599.      * @param array                   $data    Data
  1600.      *
  1601.      * @return void
  1602.      */
  1603.     protected function setAttributeValueHidden(
  1604.         \XLite\Model\Repo\ARepo $repo,
  1605.         \XLite\Model\Product $product,
  1606.         array $data,
  1607.         bool $flush true
  1608.     ) {
  1609.         $value $data['value'] ?? [];
  1610.         if (is_array($value)) {
  1611.             $value end($value);
  1612.         }
  1613.         $value trim($value);
  1614.         if (strlen($value) != 0) {
  1615.             $this->setAttributeValueHiddenItem($repo$product$data$value$flush);
  1616.         } else {
  1617.             $attributeValue $repo->findOneBy(
  1618.                 [
  1619.                     'attribute' => $this,
  1620.                     'product' => $product,
  1621.                 ]
  1622.             );
  1623.             if ($attributeValue) {
  1624.                 $repo->delete($attributeValue);
  1625.             }
  1626.         }
  1627.     }
  1628.     /**
  1629.      * Set hidden attribute item
  1630.      *
  1631.      * @param \XLite\Model\Repo\ARepo $repo    Repository
  1632.      * @param \XLite\Model\Product    $product Product
  1633.      * @param array                   $data    Data
  1634.      * @param mixed                   $value   Attribute value
  1635.      *
  1636.      * @return \XLite\Model\AttributeValue\AttributeValueHidden
  1637.      */
  1638.     protected function setAttributeValueHiddenItem(
  1639.         \XLite\Model\Repo\ARepo $repo,
  1640.         \XLite\Model\Product $product,
  1641.         array $data,
  1642.         $value,
  1643.         bool $flush true
  1644.     ) {
  1645.         $attributeValue $repo->findOneBy(
  1646.             [
  1647.                 'attribute' => $this,
  1648.                 'product' => $product,
  1649.             ]
  1650.         );
  1651.         $attributeOption Database::getRepo(AttributeOption::class)
  1652.             ->findOneByNameAndAttribute($value$this);
  1653.         if (!$attributeOption) {
  1654.             $attributeOption $this->createAttributeOption($value);
  1655.         }
  1656.         if (!$attributeValue) {
  1657.             $attributeValue $this->createAttributeValue($product);
  1658.             $product->addAttributeValueH($attributeValue);
  1659.         }
  1660.         if ($attributeValue) {
  1661.             $attributeValue->setAttributeOption($attributeOption);
  1662.             if ($flush) {
  1663.                 Database::getEM()->flush();
  1664.             }
  1665.         }
  1666.         return $attributeValue;
  1667.     }
  1668.     /**
  1669.      * Set attribute value (default)
  1670.      *
  1671.      * @param \XLite\Model\Repo\ARepo $repo    Repository
  1672.      * @param \XLite\Model\Product    $product Product
  1673.      * @param mixed                   $data    Data
  1674.      *
  1675.      * @return \XLite\Model\AttributeValue\AttributeValueText
  1676.      */
  1677.     protected function setAttributeValueDefault(
  1678.         \XLite\Model\Repo\ARepo $repo,
  1679.         \XLite\Model\Product $product,
  1680.         $data,
  1681.         bool $flush true
  1682.     ) {
  1683.         $editable is_array($data) && $this->getType() === static::TYPE_TEXT && isset($data['editable'])
  1684.             ? (bool) preg_match('/^1|yes|y|on$/iS'$data['editable'])
  1685.             : null;
  1686.         $value is_array($data) ? $data['value'] : $data;
  1687.         $value is_null($value) ? '' $value;
  1688.         if (is_array($value)) {
  1689.             $value array_shift($value);
  1690.         }
  1691.         $delete true;
  1692.         $attributeValue null;
  1693.         if ($value !== '' || $editable !== null || $this->getType() === static::TYPE_TEXT) {
  1694.             $attributeValue $repo->findOneBy(['product' => $product'attribute' => $this]);
  1695.             if (!$attributeValue) {
  1696.                 $attributeValue $this->createAttributeValue($product);
  1697.                 $delete false;
  1698.             }
  1699.             $attributeValue->setValue($value);
  1700.             if ($editable !== null) {
  1701.                 $attributeValue->setEditable($editable);
  1702.             }
  1703.         }
  1704.         if ($delete) {
  1705.             foreach ($repo->findBy(['product' => $product'attribute' => $this]) as $data) {
  1706.                 if (!$attributeValue || $attributeValue->getId() !== $data->getId()) {
  1707.                     $repo->delete($datafalse);
  1708.                 }
  1709.             }
  1710.         }
  1711.         return $attributeValue;
  1712.     }
  1713.     // }}}
  1714.     /**
  1715.      * Get id
  1716.      *
  1717.      * @return integer
  1718.      */
  1719.     public function getId()
  1720.     {
  1721.         return $this->id;
  1722.     }
  1723.     /**
  1724.      * Set decimals
  1725.      *
  1726.      * @param integer $decimals
  1727.      * @return Attribute
  1728.      */
  1729.     public function setDecimals($decimals)
  1730.     {
  1731.         $this->decimals $decimals;
  1732.         return $this;
  1733.     }
  1734.     /**
  1735.      * Get decimals
  1736.      *
  1737.      * @return integer
  1738.      */
  1739.     public function getDecimals()
  1740.     {
  1741.         return $this->decimals;
  1742.     }
  1743.     /**
  1744.      * Get type
  1745.      *
  1746.      * @return string
  1747.      */
  1748.     public function getType()
  1749.     {
  1750.         return $this->type;
  1751.     }
  1752.     /**
  1753.      * Get display mode
  1754.      *
  1755.      * @param \XLite\Model\Product $product Product OPTIONAL
  1756.      * @return string
  1757.      */
  1758.     public function getDisplayMode($product null)
  1759.     {
  1760.         $productId $product
  1761.             $product->getId()
  1762.             : \XLite\Core\Request::getInstance()->product_id;
  1763.         $prop $this->getProductAttributeProperty($productId);
  1764.         if ($prop && $prop->getDisplayMode()) {
  1765.             return $prop->getDisplayMode();
  1766.         }
  1767.         return $this->displayMode;
  1768.     }
  1769.     /**
  1770.      * @param $productId
  1771.      *
  1772.      * @return null|\XLite\Model\AttributeProperty
  1773.      */
  1774.     protected function getProductAttributeProperty($productId)
  1775.     {
  1776.         return $this->executeCachedRuntime(function () use ($productId) {
  1777.             $property null;
  1778.             if (
  1779.                 $productId
  1780.                 && ($product Database::getRepo(\XLite\Model\Product::class)->find($productId))
  1781.             ) {
  1782.                 $property Database::getRepo(\XLite\Model\AttributeProperty::class)->findOneBy([
  1783.                     'product' => $product,
  1784.                     'attribute'  => $this,
  1785.                 ]);
  1786.             }
  1787.             return $property;
  1788.         }, ['getProductAttributeProperty'$this->getId(), $productId]);
  1789.     }
  1790.     /**
  1791.      * Set display mode
  1792.      *
  1793.      * @param string $value
  1794.      * @param boolean $isNew New attribute flag OPTIONAL
  1795.      *
  1796.      * @return Attribute
  1797.      */
  1798.     public function setDisplayMode($value$isNew false)
  1799.     {
  1800.         if (
  1801.             $this->displayMode !== $value
  1802.             && $this->getAttributeProperties()
  1803.             && (!\XLite\Core\Request::getInstance()->product_id
  1804.                 || $isNew)
  1805.         ) {
  1806.             foreach ($this->getAttributeProperties() as $prop) {
  1807.                 $prop->setDisplayMode($value);
  1808.             }
  1809.         }
  1810.         $this->displayMode $value;
  1811.         return $this;
  1812.     }
  1813.     /**
  1814.      * Return display modes
  1815.      *
  1816.      * @return array
  1817.      */
  1818.     public static function getDisplayModes()
  1819.     {
  1820.         return [
  1821.             static::SELECT_BOX_MODE    => static::t('Selectbox'),
  1822.             static::BLOCKS_MODE        => static::t('Blocks'),
  1823.             static::SPECIFICATION_MODE => static::t('Specification'),
  1824.         ];
  1825.     }
  1826.     /**
  1827.      * Return display mode name
  1828.      *
  1829.      * @return string
  1830.      */
  1831.     public function getDisplayModeName()
  1832.     {
  1833.         $displayModes self::getDisplayModes();
  1834.         return $displayModes[$this->displayMode] ?? '';
  1835.     }
  1836.     /**
  1837.      * Set productClass
  1838.      *
  1839.      * @param \XLite\Model\ProductClass $productClass
  1840.      * @return Attribute
  1841.      */
  1842.     public function setProductClass(\XLite\Model\ProductClass $productClass null)
  1843.     {
  1844.         $this->productClass $productClass;
  1845.         return $this;
  1846.     }
  1847.     /**
  1848.      * Get productClass
  1849.      *
  1850.      * @return \XLite\Model\ProductClass
  1851.      */
  1852.     public function getProductClass()
  1853.     {
  1854.         return $this->productClass;
  1855.     }
  1856.     /**
  1857.      * Set attributeGroup
  1858.      *
  1859.      * @param \XLite\Model\AttributeGroup $attributeGroup
  1860.      * @return Attribute
  1861.      */
  1862.     public function setAttributeGroup(\XLite\Model\AttributeGroup $attributeGroup null)
  1863.     {
  1864.         $this->attributeGroup $attributeGroup;
  1865.         return $this;
  1866.     }
  1867.     /**
  1868.      * Get attributeGroup
  1869.      *
  1870.      * @return \XLite\Model\AttributeGroup
  1871.      */
  1872.     public function getAttributeGroup()
  1873.     {
  1874.         return $this->attributeGroup;
  1875.     }
  1876.     /**
  1877.      * Add attribute_options
  1878.      *
  1879.      * @param AttributeOption $attributeOptions
  1880.      *
  1881.      * @return Attribute
  1882.      */
  1883.     public function addAttributeOptions(AttributeOption $attributeOptions)
  1884.     {
  1885.         $this->attribute_options[] = $attributeOptions;
  1886.         return $this;
  1887.     }
  1888.     /**
  1889.      * Get attribute_options
  1890.      *
  1891.      * @return \Doctrine\Common\Collections\Collection
  1892.      */
  1893.     public function getAttributeOptions()
  1894.     {
  1895.         return $this->attribute_options;
  1896.     }
  1897.     /**
  1898.      * Set product
  1899.      *
  1900.      * @param \XLite\Model\Product $product
  1901.      * @return Attribute
  1902.      */
  1903.     public function setProduct(\XLite\Model\Product $product null)
  1904.     {
  1905.         $this->product $product;
  1906.         return $this;
  1907.     }
  1908.     /**
  1909.      * Get product
  1910.      *
  1911.      * @return \XLite\Model\Product
  1912.      */
  1913.     public function getProduct()
  1914.     {
  1915.         return $this->product;
  1916.     }
  1917.     /**
  1918.      * Add attribute property
  1919.      *
  1920.      * @param \XLite\Model\AttributeProperty $attributeProperty
  1921.      * @return Attribute
  1922.      */
  1923.     public function addAttributeProperty(\XLite\Model\AttributeProperty $attributeProperty)
  1924.     {
  1925.         $this->attribute_properties[] = $attributeProperty;
  1926.         return $this;
  1927.     }
  1928.     /**
  1929.      * Get attribute_properties
  1930.      *
  1931.      * @return \Doctrine\Common\Collections\Collection
  1932.      */
  1933.     public function getAttributeProperties()
  1934.     {
  1935.         return $this->attribute_properties;
  1936.     }
  1937.     // {{{ Translation Getters / setters
  1938.     /**
  1939.      * @return string
  1940.      */
  1941.     public function getUnit()
  1942.     {
  1943.         return $this->getTranslationField(__FUNCTION__);
  1944.     }
  1945.     /**
  1946.      * @param string $unit
  1947.      *
  1948.      * @return \XLite\Model\Base\Translation
  1949.      */
  1950.     public function setUnit($unit)
  1951.     {
  1952.         return $this->setTranslationField(__FUNCTION__$unit);
  1953.     }
  1954.     // }}}
  1955.     public function setName($name)
  1956.     {
  1957.         // Optimized check with precalculated name
  1958.         if ($this->getName() === $name) {
  1959.             return $this;
  1960.         }
  1961.         return parent::setName($name);
  1962.     }
  1963.     public function getName()
  1964.     {
  1965.         return $this->precalculatedName ?? parent::getName();
  1966.     }
  1967.     public function setPrecalculatedName(?string $precalculatedName): void
  1968.     {
  1969.         $this->precalculatedName $precalculatedName;
  1970.     }
  1971.     public function hasAddToNew(): bool
  1972.     {
  1973.         if ($this->getAddToNew() && count($this->getAddToNew()) > 1) {
  1974.             return true;
  1975.         }
  1976.         if ($this->getType() === static::TYPE_SELECT) {
  1977.             $optionsCount $this->getAddToNewOptionsCount();
  1978.             if ($optionsCount 1) {
  1979.                 return true;
  1980.             }
  1981.         }
  1982.         return false;
  1983.     }
  1984.     public function setPrecalculatedAddToNewOptionsCount(?int $precalculatedAddToNewOptionsCount): void
  1985.     {
  1986.         $this->precalculatedAddToNewOptionsCount $precalculatedAddToNewOptionsCount;
  1987.     }
  1988.     protected function getAddToNewOptionsCount(): int
  1989.     {
  1990.         return $this->precalculatedAddToNewOptionsCount ?? $this->calculateAddToNewOptionsCount();
  1991.     }
  1992.     protected function calculateAddToNewOptionsCount(): int
  1993.     {
  1994.         if ($this->getType() !== static::TYPE_SELECT) {
  1995.             return 0;
  1996.         }
  1997.         return Database::getRepo(AttributeOption::class)?->countBy(
  1998.             [
  1999.                 'attribute' => $this,
  2000.                 'addToNew'  => true,
  2001.             ]
  2002.         );
  2003.     }
  2004. }