classes/XLite/Model/Attribute.php line 690

Open in your IDE?
  1. <?php
  2. /**
  3.  * Copyright (c) 2011-present Qualiteam software Ltd. 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.     /**
  809.      * Return name of widget class
  810.      *
  811.      * @param string $type      Attribute type
  812.      * @param string $interface Interface (Admin | Customer) OPTIONAL
  813.      *
  814.      * @return string
  815.      */
  816.     public static function getWidgetClass($type$interface null)
  817.     {
  818.         if ($interface === null) {
  819.             $interface \XLite::isAdminZone() ? 'Admin' 'Customer';
  820.         }
  821.         return '\XLite\View\Product\AttributeValue\\'
  822.             $interface
  823.             '\\'
  824.             . static::getTypes($typetrue);
  825.     }
  826.     /**
  827.      * Return name of value class
  828.      *
  829.      * @param string $type Type
  830.      *
  831.      * @return string
  832.      */
  833.     public static function getAttributeValueClass($type)
  834.     {
  835.         return '\XLite\Model\AttributeValue\AttributeValue'
  836.             . static::getTypes($typetrue);
  837.     }
  838.     /**
  839.      * Constructor
  840.      *
  841.      * @param array $data Entity properties OPTIONAL
  842.      */
  843.     public function __construct(array $data = [])
  844.     {
  845.         $this->attribute_options = new \Doctrine\Common\Collections\ArrayCollection();
  846.         parent::__construct($data);
  847.     }
  848.     /**
  849.      * Return number of products associated with this attribute
  850.      *
  851.      * @return integer
  852.      */
  853.     public function getProductsCount()
  854.     {
  855.         return $this->getClass()->getProductsCount();
  856.     }
  857.     /**
  858.      * Return list of types or type
  859.      *
  860.      * @param string  $type              Type OPTIONAL
  861.      * @param boolean $returnServiceType Return service type OPTIONAL
  862.      *
  863.      * @return array | string
  864.      */
  865.     public static function getTypes($type null$returnServiceType false)
  866.     {
  867.         $list = [
  868.             static::TYPE_SELECT   => static::t('Plain field'),
  869.             static::TYPE_TEXT     => static::t('Textarea'),
  870.             static::TYPE_CHECKBOX => static::t('Yes/No'),
  871.             static::TYPE_HIDDEN   => static::t('Hidden field'),
  872.         ];
  873.         $listServiceTypes = [
  874.             static::TYPE_SELECT   => 'Select',
  875.             static::TYPE_TEXT     => 'Text',
  876.             static::TYPE_CHECKBOX => 'Checkbox',
  877.             static::TYPE_HIDDEN   => 'Hidden',
  878.         ];
  879.         $list $returnServiceType $listServiceTypes $list;
  880.         return $type !== null
  881.             ? ($list[$type] ?? null)
  882.             : $list;
  883.     }
  884.     /**
  885.      * Return list of 'addToNew' types
  886.      *
  887.      * @return array
  888.      */
  889.     public static function getAddToNewTypes()
  890.     {
  891.         return [
  892.             static::ADD_TO_NEW_YES,
  893.             static::ADD_TO_NEW_NO,
  894.             static::ADD_TO_NEW_YES_NO,
  895.         ];
  896.     }
  897.     /**
  898.      * Return values associated with this attribute
  899.      *
  900.      * @return list<\Xlite\Model\AttributeValue\AAttributeValue>
  901.      */
  902.     public function getAttributeValues()
  903.     {
  904.         $cnd = new \XLite\Core\CommonCell();
  905.         $cnd->attribute $this;
  906.         return Database::getRepo(static::getAttributeValueClass($this->getType()))
  907.             ->search($cnd);
  908.     }
  909.     /**
  910.      * Return number of values associated with this attribute
  911.      *
  912.      * @return integer
  913.      */
  914.     public function getAttributeValuesCount()
  915.     {
  916.         $cnd = new \XLite\Core\CommonCell();
  917.         $cnd->attribute $this;
  918.         return Database::getRepo(static::getAttributeValueClass($this->getType()))
  919.             ->search($cndtrue);
  920.     }
  921.     /**
  922.      * Set 'addToNew' value
  923.      *
  924.      * @param string|array $value Value
  925.      *
  926.      * @return void
  927.      */
  928.     public function setAddToNew($value)
  929.     {
  930.         if (
  931.             is_array($value)
  932.             && $this->getType() === static::TYPE_CHECKBOX
  933.         ) {
  934.             if (count($value) === 2) {
  935.                 $value = static::ADD_TO_NEW_YES_NO;
  936.             } elseif (count($value) === 1) {
  937.                 $value array_shift($value) ? static::ADD_TO_NEW_YES : static::ADD_TO_NEW_NO;
  938.             }
  939.         }
  940.         $this->addToNew in_array($value, static::getAddToNewTypes()) ? $value '';
  941.     }
  942.     /**
  943.      * Get 'addToNew' value
  944.      *
  945.      * @return array
  946.      */
  947.     public function getAddToNew()
  948.     {
  949.         $value null;
  950.         if ($this->getType() === static::TYPE_CHECKBOX) {
  951.             switch ($this->addToNew) {
  952.                 case static::ADD_TO_NEW_YES:
  953.                     $value = [1];
  954.                     break;
  955.                 case static::ADD_TO_NEW_NO:
  956.                     $value = [0];
  957.                     break;
  958.                 case static::ADD_TO_NEW_YES_NO:
  959.                     $value = [01];
  960.                     break;
  961.                 default:
  962.             }
  963.         }
  964.         return $value;
  965.     }
  966.     /**
  967.      * Set type
  968.      *
  969.      * @param string $type Type
  970.      *
  971.      * @return void
  972.      */
  973.     public function setType($type)
  974.     {
  975.         $types = static::getTypes();
  976.         if (isset($types[$type])) {
  977.             if (
  978.                 $this->type
  979.                 && $type != $this->type
  980.                 && $this->getId()
  981.             ) {
  982.                 foreach ($this->getAttributeOptions() as $option) {
  983.                     Database::getEM()->remove($option);
  984.                 }
  985.                 foreach ($this->getAttributeValues() as $value) {
  986.                     Database::getEM()->remove($value);
  987.                 }
  988.             }
  989.             $this->type $type;
  990.         }
  991.     }
  992.     /**
  993.      * Return product property (return new property if property does not exist)
  994.      *
  995.      * @param \XLite\Model\Product $product Product OPTIONAL
  996.      *
  997.      * @return \XLite\Model\AttributeProperty
  998.      */
  999.     public function getProperty($product)
  1000.     {
  1001.         return $this->executeCachedRuntime(function () use ($product) {
  1002.             $property null;
  1003.             if ($product->getProductId()) {
  1004.                 $product Database::getRepo(\XLite\Model\Product::class)?->find($product->getProductId());
  1005.                 $property Database::getRepo(\XLite\Model\AttributeProperty::class)?->findOneBy([
  1006.                     'product' => $product,
  1007.                     'attribute'  => $this,
  1008.                 ]);
  1009.                 if ($property === null) {
  1010.                     $property $this->getNewProperty($product);
  1011.                 }
  1012.             }
  1013.             return $property;
  1014.         }, ['getProperty'$this->getId(), $product->getProductId()]);
  1015.     }
  1016.     /**
  1017.      * Return new product property
  1018.      *
  1019.      * @param \XLite\Model\Product $product Product OPTIONAL
  1020.      *
  1021.      * @return \XLite\Model\AttributeProperty
  1022.      */
  1023.     protected function getNewProperty($product)
  1024.     {
  1025.         $result = new \XLite\Model\AttributeProperty();
  1026.         $result->setAttribute($this);
  1027.         $result->setProduct($product);
  1028.         $result->setDisplayAbove($this->getDisplayAbove());
  1029.         $this->addAttributeProperty($result);
  1030.         Database::getEM()->persist($result);
  1031.         return $result;
  1032.     }
  1033.     /**
  1034.      * Returns position
  1035.      *
  1036.      * @param \XLite\Model\Product $product Product OPTIONAL
  1037.      *
  1038.      * @return integer
  1039.      */
  1040.     public function getPosition($product null)
  1041.     {
  1042.         if ($product) {
  1043.             $result $this->getProperty($product);
  1044.             $result $result $result->getPosition() : 0;
  1045.         } else {
  1046.             $result $this->position;
  1047.         }
  1048.         return $result;
  1049.     }
  1050.     /**
  1051.      * Set the position
  1052.      *
  1053.      * @param integer|array $value
  1054.      *
  1055.      * @return void
  1056.      */
  1057.     public function setPosition($value)
  1058.     {
  1059.         if (is_array($value)) {
  1060.             $property $this->getProperty($value['product']);
  1061.             $property->setPosition($value['position']);
  1062.         } else {
  1063.             $this->position $value;
  1064.         }
  1065.     }
  1066.     /**
  1067.      * @param \XLite\Model\Product $product Product OPTIONAL
  1068.      *
  1069.      * @return integer
  1070.      */
  1071.     public function getDisplayAbove($product null)
  1072.     {
  1073.         if ($product) {
  1074.             $result $this->getProperty($product);
  1075.             $result $result $result->getDisplayAbove() : $this->displayAbove;
  1076.         } else {
  1077.             $result $this->displayAbove;
  1078.         }
  1079.         return $result;
  1080.     }
  1081.     /**
  1082.      * @param boolean|array $value
  1083.      *
  1084.      * @return void
  1085.      */
  1086.     public function setDisplayAbove($value)
  1087.     {
  1088.         if (is_array($value)) {
  1089.             $property $this->getProperty($value['product']);
  1090.             $property->setDisplayAbove($value['displayAbove']);
  1091.         } else {
  1092.             $this->displayAbove $value;
  1093.         }
  1094.     }
  1095.     /**
  1096.      * Add to new product
  1097.      *
  1098.      * @param \XLite\Model\Product $product Product
  1099.      *
  1100.      * @return void
  1101.      */
  1102.     public function addToNewProduct(\XLite\Model\Product $product)
  1103.     {
  1104.         $displayAbove $this->getDisplayAbove();
  1105.         if ($this->getAddToNew()) {
  1106.             $displayAbove count($this->getAddToNew()) > ?: $displayAbove;
  1107.             foreach ($this->getAddToNew() as $value) {
  1108.                 $av $this->createAttributeValue($product);
  1109.                 if ($av) {
  1110.                     $av->setValue($value);
  1111.                 }
  1112.             }
  1113.         } elseif ($this->getType() === static::TYPE_SELECT) {
  1114.             $attributeOptions Database::getRepo(\XLite\Model\AttributeOption::class)->findBy(
  1115.                 [
  1116.                     'attribute' => $this,
  1117.                     'addToNew'  => true,
  1118.                 ],
  1119.                 ['position' => 'ASC']
  1120.             );
  1121.             $displayAbove count($attributeOptions) > ?: $displayAbove;
  1122.             foreach ($attributeOptions as $attributeOption) {
  1123.                 $av $this->createAttributeValue($product);
  1124.                 if ($av) {
  1125.                     $av->setAttributeOption($attributeOption);
  1126.                     $av->setPosition($attributeOption->getPosition());
  1127.                 }
  1128.             }
  1129.         } elseif ($this->getType() === static::TYPE_TEXT) {
  1130.             $av $this->createAttributeValue($product);
  1131.             if ($av) {
  1132.                 $av->setEditable(false);
  1133.                 $av->setValue('');
  1134.             }
  1135.         } elseif ($this->getType() === static::TYPE_HIDDEN) {
  1136.             $attributeOption Database::getRepo(\XLite\Model\AttributeOption::class)->findOneBy(
  1137.                 [
  1138.                     'attribute' => $this,
  1139.                     'addToNew'  => true,
  1140.                 ]
  1141.             );
  1142.             if ($attributeOption) {
  1143.                 $av $this->createAttributeValue($product);
  1144.                 if ($av) {
  1145.                     $av->setAttributeOption($attributeOption);
  1146.                 }
  1147.             }
  1148.         }
  1149.         $this->setDisplayAbove(
  1150.             [
  1151.                 'product' => $product,
  1152.                 'displayAbove' => $displayAbove,
  1153.             ]
  1154.         );
  1155.     }
  1156.     /**
  1157.      * Apply changes
  1158.      *
  1159.      * @param \XLite\Model\Product $product Product
  1160.      * @param mixed                $changes Changes
  1161.      *
  1162.      * @return void
  1163.      */
  1164.     public function applyChanges(\XLite\Model\Product $product$changes)
  1165.     {
  1166.         if (
  1167.             (
  1168.                 !$this->getProductClass()
  1169.                 && !$this->getProduct()
  1170.             )
  1171.             || (
  1172.                 $this->getProductClass()
  1173.                 && $product->getProductClass()
  1174.                 && $this->getProductClass()->getId() == $product->getProductClass()->getId()
  1175.             )
  1176.             || ($this->getProduct()
  1177.                 && $this->getProduct()->getId() == $product->getId()
  1178.             )
  1179.         ) {
  1180.             $class = static::getAttributeValueClass($this->getType());
  1181.             $repo Database::getRepo($class);
  1182.             switch ($this->getType()) {
  1183.                 case static::TYPE_TEXT:
  1184.                     $this->setAttributeValue($product$changes);
  1185.                     break;
  1186.                 case static::TYPE_CHECKBOX:
  1187.                 case static::TYPE_SELECT:
  1188.                     foreach ($repo->findBy(['product' => $product'attribute' => $this]) as $av) {
  1189.                         $uniq $this->getType() === static::TYPE_CHECKBOX
  1190.                             $av->getValue()
  1191.                             : $av->getAttributeOption()->getId();
  1192.                         if (in_array($uniq$changes['deleted'])) {
  1193.                             $repo->delete($avfalse);
  1194.                         } elseif (
  1195.                             isset($changes['changed'][$uniq])
  1196.                             || isset($changes['added'][$uniq])
  1197.                         ) {
  1198.                             $data $changes['changed'][$uniq] ?? $changes['added'][$uniq];
  1199.                             if (
  1200.                                 isset($data['defaultValue'])
  1201.                                 && $data['defaultValue']
  1202.                                 && !$av->getDefaultValue()
  1203.                             ) {
  1204.                                 $pr $repo->findOneBy(
  1205.                                     [
  1206.                                         'product'      => $product,
  1207.                                         'attribute'    => $this,
  1208.                                         'defaultValue' => true,
  1209.                                     ]
  1210.                                 );
  1211.                                 if ($pr) {
  1212.                                     $pr->setDefaultValue(false);
  1213.                                 }
  1214.                             }
  1215.                             $repo->update($av$data);
  1216.                             if (isset($changes['added'][$uniq])) {
  1217.                                 unset($changes['added'][$uniq]);
  1218.                             }
  1219.                         }
  1220.                     }
  1221.                     if ($changes['added']) {
  1222.                         foreach ($changes['added'] as $uniq => $data) {
  1223.                             if (
  1224.                                 isset($data['defaultValue'])
  1225.                                 && $data['defaultValue']
  1226.                             ) {
  1227.                                 $pr $repo->findOneBy(
  1228.                                     [
  1229.                                         'product'      => $product,
  1230.                                         'attribute'    => $this,
  1231.                                         'defaultValue' => true,
  1232.                                     ]
  1233.                                 );
  1234.                                 if ($pr) {
  1235.                                     $pr->setDefaultValue(false);
  1236.                                 }
  1237.                             }
  1238.                             $av $this->createAttributeValue($product);
  1239.                             if ($av) {
  1240.                                 if ($this->getType() === static::TYPE_CHECKBOX) {
  1241.                                     $av->setValue($uniq);
  1242.                                 } else {
  1243.                                     $av->setAttributeOption(
  1244.                                         Database::getRepo(\XLite\Model\AttributeOption::class)->find($uniq)
  1245.                                     );
  1246.                                 }
  1247.                                 $repo->update($av$data);
  1248.                             }
  1249.                         }
  1250.                     }
  1251.                     break;
  1252.                 default:
  1253.             }
  1254.             Database::getEM()->flush();
  1255.         }
  1256.     }
  1257.     /**
  1258.      * Set attribute value
  1259.      *
  1260.      * @param \XLite\Model\Product $product Product
  1261.      * @param mixed                $data    Value
  1262.      *
  1263.      * @return void
  1264.      */
  1265.     public function setAttributeValue(\XLite\Model\Product $product$databool $flush true)
  1266.     {
  1267.         $repo Database::getRepo(
  1268.             static::getAttributeValueClass($this->getType())
  1269.         );
  1270.         $method $this->defineSetAttributeValueMethodName($data);
  1271.         $this->$method($repo$product$data$flush);
  1272.     }
  1273.     /**
  1274.      * Get attribute value
  1275.      *
  1276.      * @param \XLite\Model\Product $product  Product
  1277.      * @param boolean              $asString As string flag OPTIONAL
  1278.      *
  1279.      * @return mixed
  1280.      */
  1281.     public function getAttributeValue(\XLite\Model\Product $product$asString false)
  1282.     {
  1283.         $repo Database::getRepo(static::getAttributeValueClass($this->getType()));
  1284.         if (in_array($this->getType(), [static::TYPE_SELECT, static::TYPE_CHECKBOX, static::TYPE_HIDDEN])) {
  1285.             $attributeValue $repo->findBy(
  1286.                 ['product' => $product'attribute' => $this],
  1287.                 $this->getType() === static::TYPE_SELECT ? ['position' => 'ASC'] : null
  1288.             );
  1289.             if (
  1290.                 $attributeValue
  1291.                 && $asString
  1292.             ) {
  1293.                 if (is_array($attributeValue)) {
  1294.                     foreach ($attributeValue as $k => $v) {
  1295.                         $attributeValue[$k] = $v->asString();
  1296.                     }
  1297.                 } elseif (is_object($attributeValue)) {
  1298.                     $attributeValue $attributeValue->asString();
  1299.                 } elseif ($this->getType() === static::TYPE_CHECKBOX) {
  1300.                     $attributeValue = static::t('Yes');
  1301.                 }
  1302.             }
  1303.         } else {
  1304.             $attributeValue $repo->findOneBy(
  1305.                 ['product' => $product'attribute' => $this]
  1306.             );
  1307.             if ($attributeValue && $asString) {
  1308.                 $attributeValue $attributeValue->getValue();
  1309.             }
  1310.         }
  1311.         return $attributeValue;
  1312.     }
  1313.     /**
  1314.      * Get attribute value
  1315.      *
  1316.      * @param \XLite\Model\Product $product Product
  1317.      *
  1318.      * @return \XLite\Model\AttributeValue\AAttributeValue
  1319.      */
  1320.     public function getDefaultAttributeValue(\XLite\Model\Product $product)
  1321.     {
  1322.         $repo Database::getRepo(static::getAttributeValueClass($this->getType()));
  1323.         $attributeValue $repo->findOneBy(['product' => $product'attribute' => $this'defaultValue' => true]);
  1324.         if (!$attributeValue) {
  1325.             $attributeValue $repo->findDefaultAttributeValue(['product' => $product'attribute' => $this]);
  1326.         }
  1327.         return $attributeValue;
  1328.     }
  1329.     /**
  1330.      * This attribute is multiple or not flag
  1331.      *
  1332.      * @param \XLite\Model\Product $product Product
  1333.      *
  1334.      * @return boolean
  1335.      */
  1336.     public function isMultiple(\XLite\Model\Product $product)
  1337.     {
  1338.         $repo Database::getRepo(static::getAttributeValueClass($this->getType()));
  1339.         return (!$this->getProduct() || $this->getProduct()->getId() == $product->getId())
  1340.             && (!$this->getProductClass()
  1341.                 || ($product->getProductClass()
  1342.                     && $this->getProductClass()->getId() == $product->getProductClass()->getId()
  1343.                 )
  1344.             )
  1345.             && count($repo->findBy(['product' => $product'attribute' => $this]));
  1346.     }
  1347.     /**
  1348.      * This attribute is hidden or not flag
  1349.      *
  1350.      * @return bool
  1351.      */
  1352.     public function isHidden()
  1353.     {
  1354.         return $this->getType() === static::TYPE_HIDDEN;
  1355.     }
  1356.     /**
  1357.      * Create attribute value
  1358.      *
  1359.      * @param \XLite\Model\Product $product Product
  1360.      *
  1361.      * @return mixed
  1362.      */
  1363.     protected function createAttributeValue(\XLite\Model\Product $product)
  1364.     {
  1365.         $class = static::getAttributeValueClass($this->getType());
  1366.         $attributeValue = new $class();
  1367.         $attributeValue->setProduct($product);
  1368.         $attributeValue->setAttribute($this);
  1369.         Database::getEM()->persist($attributeValue);
  1370.         return $attributeValue;
  1371.     }
  1372.     /**
  1373.      * Create attribute option
  1374.      *
  1375.      * @param string $value Option name
  1376.      *
  1377.      * @return \XLite\Model\AttributeOption
  1378.      */
  1379.     protected function createAttributeOption($value)
  1380.     {
  1381.         $attributeOption = new \XLite\Model\AttributeOption();
  1382.         $attributeOption->setAttribute($this);
  1383.         $attributeOption->setName($value);
  1384.         Database::getEM()->persist($attributeOption);
  1385.         return $attributeOption;
  1386.     }
  1387.     // {{{ Set attribute value
  1388.     /**
  1389.      * Define method name for 'setAttributeValue' operation
  1390.      *
  1391.      * @param mixed $data Data
  1392.      *
  1393.      * @return string
  1394.      */
  1395.     protected function defineSetAttributeValueMethodName($data)
  1396.     {
  1397.         if ($this->getType() === static::TYPE_SELECT) {
  1398.             $result 'setAttributeValueSelect';
  1399.         } elseif ($this->getType() === static::TYPE_CHECKBOX && isset($data['multiple']) && $data['multiple']) {
  1400.             $result 'setAttributeValueCheckbox';
  1401.         } elseif ($this->getType() === static::TYPE_HIDDEN) {
  1402.             $result 'setAttributeValueHidden';
  1403.         } else {
  1404.             $result 'setAttributeValueDefault';
  1405.         }
  1406.         return $result;
  1407.     }
  1408.     /**
  1409.      * Set attribute value (select)
  1410.      *
  1411.      * @param \XLite\Model\Repo\ARepo $repo    Repository
  1412.      * @param \XLite\Model\Product    $product Product
  1413.      * @param array                   $data    Data
  1414.      *
  1415.      * @return void
  1416.      */
  1417.     protected function setAttributeValueSelect(
  1418.         \XLite\Model\Repo\ARepo $repo,
  1419.         \XLite\Model\Product $product,
  1420.         array $data,
  1421.         bool $flush true
  1422.     ) {
  1423.         $ids = [];
  1424.         $values $data['value'] ?? [];
  1425.         krsort($values);
  1426.         foreach ($values as $id => $value) {
  1427.             $value trim($value);
  1428.             if (strlen($value) > && is_int($id)) {
  1429.                 if (!isset($data['deleteValue'][$id])) {
  1430.                     [$avId] = $this->setAttributeValueSelectItem($repo$product$data$id$value$flush);
  1431.                     $ids[$avId] = $avId;
  1432.                 }
  1433.                 if (!isset($data['multiple'])) {
  1434.                     break;
  1435.                 }
  1436.             }
  1437.         }
  1438.         // Make a performance curtsy, especially to import.
  1439.         // We don't need to do anything with EM if the collection hasn't been initialized (fetched from DB).
  1440.         $isCollectionInitialized $product
  1441.             && $product->getAttributeValueS() instanceof PersistentCollection
  1442.             && $product->getAttributeValueS()->isInitialized();
  1443.         foreach ($repo->findBy(['product' => $product'attribute' => $this]) as $data) {
  1444.             if ($data->getId() && !isset($ids[$data->getId()])) {
  1445.                 $repo->delete($datafalse);
  1446.                 if ($isCollectionInitialized) {
  1447.                     // We have to delete child entities in EM otherwise it will be cascadePersisted again (unmark2delete)
  1448.                     // for ex. here \XLite\Logic\Import\Processor\AProcessor::importData()->\XLite\Core\Database::getEM()->persist($model); XCB-2770
  1449.                     // according to doctrine doc https://www.doctrine-project.org/projects/doctrine-orm/en/2.17/reference/working-with-objects.html#persisting-entities
  1450.                     $product->getAttributeValueS()->removeElement($data);
  1451.                 }
  1452.             }
  1453.         }
  1454.     }
  1455.     protected function setAttributeValueSelectItem(
  1456.         \XLite\Model\Repo\ARepo $repo,
  1457.         \XLite\Model\Product $product,
  1458.         array $data,
  1459.         $id,
  1460.         $value,
  1461.         bool $flush true
  1462.     ) {
  1463.         static $runtimeCacheAttributeValues = [];
  1464.         static $runtimeCacheAttributeOptions = [];
  1465.         $result = [nullnullnull];
  1466.         $attributeValue $this->getAttributeOptionByValue($repo$data$id$value);
  1467.         $attributeOption $this->getAttributeOption($value$attributeValue$runtimeCacheAttributeOptions);
  1468.         if (!$attributeValue) {
  1469.             $attributeValue $this->findOrCreateAttributeValue($repo$attributeOption$product$id$data$runtimeCacheAttributeValues);
  1470.         }
  1471.         if ($attributeValue) {
  1472.             $this->processAttributeValue($attributeValue$attributeOption$data$id);
  1473.             if ($flush) {
  1474.                 Database::getEM()->flush();
  1475.             }
  1476.             $result = [
  1477.                 $attributeValue->getId(),
  1478.                 $attributeValue,
  1479.                 $attributeOption,
  1480.             ];
  1481.         }
  1482.         return $result;
  1483.     }
  1484.     protected function getAttributeOptionByValue(Arepo $repo, array $data, ?int $id, ?string $value): ?AAttributeValue
  1485.     {
  1486.         if ($id &&  !isset($data['ignoreIds']) && $this->getProduct()) {
  1487.             $attributeValue $repo->find($id);
  1488.             if ($attributeValue) {
  1489.                 $attributeOption $attributeValue->getAttributeOption();
  1490.                 $attributeOption->setName($value);
  1491.                 return $attributeValue;
  1492.             }
  1493.         }
  1494.         return null;
  1495.     }
  1496.     protected function getAttributeOption(
  1497.         ?string $value,
  1498.         ?AAttributeValue $attributeValue,
  1499.         array &$attributeOptionsRuntimeCache
  1500.     ): AttributeOption {
  1501.         $attributeOptionKey spl_object_id($this) . $value;
  1502.         if ($attributeOption $attributeOptionsRuntimeCache[$attributeOptionKey] ?? null) {
  1503.             return $attributeOption;
  1504.         }
  1505.         if ($attributeValue) {
  1506.             return $attributeValue->getAttributeOption();
  1507.         }
  1508.         $attributeOption Database::getRepo(\XLite\Model\AttributeOption::class)
  1509.             ->findOneByNameAndAttribute($value$this);
  1510.         if ($attributeOption) {
  1511.             return $attributeOption;
  1512.         }
  1513.         $attributeOption $this->createAttributeOption($value);
  1514.         $attributeOptionsRuntimeCache[$attributeOptionKey] = $attributeOption;
  1515.         return $attributeOption;
  1516.     }
  1517.     protected function findOrCreateAttributeValue(
  1518.         ARepo $repo,
  1519.         AttributeOption $attributeOption,
  1520.         Product $product,
  1521.         ?int $id,
  1522.         array $data,
  1523.         array &$runtimeCacheAttributeValues = []
  1524.     ): AAttributeValue {
  1525.         $attributeValueCacheKey spl_object_id($product) . $attributeOption?->getId();
  1526.         $attributeValue $repo->findOneBy([
  1527.             'attribute_option' => $attributeOption,
  1528.             'product' => $product,
  1529.         ]);
  1530.         if (!$attributeValue && $id && !isset($data['ignoreIds'])) {
  1531.             $attributeValue $repo->find($id);
  1532.         } elseif (isset($runtimeCacheAttributeValues[$attributeValueCacheKey]) && $attributeOption?->getId()) {
  1533.             $attributeValue $runtimeCacheAttributeValues[$attributeValueCacheKey];
  1534.         }
  1535.         if (!$attributeValue) {
  1536.             $attributeValue $this->createAttributeValue($product);
  1537.             $this->setAttributeValuePosition($attributeValue$product);
  1538.             $product->addAttributeValueS($attributeValue);
  1539.             $runtimeCacheAttributeValues[$attributeValueCacheKey] = $attributeValue;
  1540.         }
  1541.         return $attributeValue;
  1542.     }
  1543.     protected function setAttributeValuePosition(AAttributeValue $attributeValueProduct $product): void
  1544.     {
  1545.         $attributeValue->setPosition(
  1546.             array_reduce(
  1547.                 $product->getAttributeValueS()->toArray(),
  1548.                 function ($carryAttributeValueSelect $item) {
  1549.                     return $item->getAttribute() === $this max($carry$item->getPosition()) : $carry;
  1550.                 },
  1551.                 0
  1552.             ) + 10
  1553.         );
  1554.     }
  1555.     protected function processAttributeValue($attributeValue$attributeOption$data$id)
  1556.     {
  1557.         $attributeValue->setAttributeOption($attributeOption);
  1558.         $attributeValue->setDefaultValue(isset($data['default'][$id]));
  1559.         foreach ($attributeValue::getModifiers() as $modifier => $options) {
  1560.             if (isset($data[$modifier][$id])) {
  1561.                 $attributeValue->setModifier($data[$modifier][$id], $modifier);
  1562.             }
  1563.         }
  1564.     }
  1565.     /**
  1566.      * Set attribute value (checkbox)
  1567.      *
  1568.      * @param \XLite\Model\Repo\ARepo $repo    Repository
  1569.      * @param \XLite\Model\Product    $product Product
  1570.      * @param array                   $data    Data
  1571.      *
  1572.      * @return void
  1573.      */
  1574.     protected function setAttributeValueCheckbox(
  1575.         \XLite\Model\Repo\ARepo $repo,
  1576.         \XLite\Model\Product $product,
  1577.         array $data,
  1578.         bool $flush true
  1579.     ) {
  1580.         foreach ([truefalse] as $value) {
  1581.             $this->setAttributeValueCheckboxItem($repo$product$data$value);
  1582.         }
  1583.     }
  1584.     /**
  1585.      * Set attribute value (checkbox item)
  1586.      *
  1587.      * @param \XLite\Model\Repo\ARepo $repo    Repository
  1588.      * @param \XLite\Model\Product    $product Product
  1589.      * @param array                   $data    Data
  1590.      * @param boolean|int             $value   Item value
  1591.      *
  1592.      * @return \XLite\Model\AttributeValue\AttributeValueCheckbox
  1593.      */
  1594.     protected function setAttributeValueCheckboxItem(
  1595.         \XLite\Model\Repo\ARepo $repo,
  1596.         \XLite\Model\Product $product,
  1597.         array $data,
  1598.         $value
  1599.     ) {
  1600.         $attributeValue $repo->findOneBy(
  1601.             [
  1602.                 'product'   => $product,
  1603.                 'attribute' => $this,
  1604.                 'value'     => $value,
  1605.             ]
  1606.         );
  1607.         if (!$attributeValue) {
  1608.             $attributeValue $this->createAttributeValue($product);
  1609.             $attributeValue->setValue($value);
  1610.         }
  1611.         $value = (int) $value;
  1612.         $attributeValue->setDefaultValue(isset($data['default'][$value]));
  1613.         foreach ($attributeValue::getModifiers() as $modifier => $options) {
  1614.             if (isset($data[$modifier]) && isset($data[$modifier][$value])) {
  1615.                 $attributeValue->setModifier($data[$modifier][$value], $modifier);
  1616.             }
  1617.         }
  1618.         return $attributeValue;
  1619.     }
  1620.     /**
  1621.      * Set attribute value (hidden)
  1622.      *
  1623.      * @param \XLite\Model\Repo\ARepo $repo    Repository
  1624.      * @param \XLite\Model\Product    $product Product
  1625.      * @param array                   $data    Data
  1626.      *
  1627.      * @return void
  1628.      */
  1629.     protected function setAttributeValueHidden(
  1630.         \XLite\Model\Repo\ARepo $repo,
  1631.         \XLite\Model\Product $product,
  1632.         array $data,
  1633.         bool $flush true
  1634.     ) {
  1635.         $value $data['value'] ?? [];
  1636.         if (is_array($value)) {
  1637.             $value end($value);
  1638.         }
  1639.         $value trim($value);
  1640.         if (strlen($value) != 0) {
  1641.             $this->setAttributeValueHiddenItem($repo$product$data$value$flush);
  1642.         } else {
  1643.             $attributeValue $repo->findOneBy(
  1644.                 [
  1645.                     'attribute' => $this,
  1646.                     'product' => $product,
  1647.                 ]
  1648.             );
  1649.             if ($attributeValue) {
  1650.                 $repo->delete($attributeValue);
  1651.             }
  1652.         }
  1653.     }
  1654.     /**
  1655.      * Set hidden attribute item
  1656.      *
  1657.      * @param \XLite\Model\Repo\ARepo $repo    Repository
  1658.      * @param \XLite\Model\Product    $product Product
  1659.      * @param array                   $data    Data
  1660.      * @param mixed                   $value   Attribute value
  1661.      *
  1662.      * @return \XLite\Model\AttributeValue\AttributeValueHidden
  1663.      */
  1664.     protected function setAttributeValueHiddenItem(
  1665.         \XLite\Model\Repo\ARepo $repo,
  1666.         \XLite\Model\Product $product,
  1667.         array $data,
  1668.         $value,
  1669.         bool $flush true
  1670.     ) {
  1671.         $attributeValue $repo->findOneBy(
  1672.             [
  1673.                 'attribute' => $this,
  1674.                 'product' => $product,
  1675.             ]
  1676.         );
  1677.         $attributeOption Database::getRepo(\XLite\Model\AttributeOption::class)
  1678.             ->findOneByNameAndAttribute($value$this);
  1679.         if (!$attributeOption) {
  1680.             $attributeOption $this->createAttributeOption($value);
  1681.         }
  1682.         if (!$attributeValue) {
  1683.             $attributeValue $this->createAttributeValue($product);
  1684.             $product->addAttributeValueH($attributeValue);
  1685.         }
  1686.         if ($attributeValue) {
  1687.             $attributeValue->setAttributeOption($attributeOption);
  1688.             if ($flush) {
  1689.                 Database::getEM()->flush();
  1690.             }
  1691.         }
  1692.         return $attributeValue;
  1693.     }
  1694.     /**
  1695.      * Set attribute value (default)
  1696.      *
  1697.      * @param \XLite\Model\Repo\ARepo $repo    Repository
  1698.      * @param \XLite\Model\Product    $product Product
  1699.      * @param mixed                   $data    Data
  1700.      *
  1701.      * @return \XLite\Model\AttributeValue\AttributeValueText
  1702.      */
  1703.     protected function setAttributeValueDefault(
  1704.         \XLite\Model\Repo\ARepo $repo,
  1705.         \XLite\Model\Product $product,
  1706.         $data,
  1707.         bool $flush true
  1708.     ) {
  1709.         $editable is_array($data) && $this->getType() === static::TYPE_TEXT && isset($data['editable'])
  1710.             ? (bool) preg_match('/^1|yes|y|on$/iS'$data['editable'])
  1711.             : null;
  1712.         $value is_array($data) ? $data['value'] : $data;
  1713.         $value is_null($value) ? '' $value;
  1714.         if (is_array($value)) {
  1715.             $value array_shift($value);
  1716.         }
  1717.         $delete true;
  1718.         $attributeValue null;
  1719.         if ($value !== '' || $editable !== null || $this->getType() === static::TYPE_TEXT) {
  1720.             $attributeValue $repo->findOneBy(['product' => $product'attribute' => $this]);
  1721.             if (!$attributeValue) {
  1722.                 $attributeValue $this->createAttributeValue($product);
  1723.                 $delete false;
  1724.             }
  1725.             $attributeValue->setValue($value);
  1726.             if ($editable !== null) {
  1727.                 $attributeValue->setEditable($editable);
  1728.             }
  1729.         }
  1730.         if ($delete) {
  1731.             foreach ($repo->findBy(['product' => $product'attribute' => $this]) as $data) {
  1732.                 if (!$attributeValue || $attributeValue->getId() !== $data->getId()) {
  1733.                     $repo->delete($datafalse);
  1734.                 }
  1735.             }
  1736.         }
  1737.         return $attributeValue;
  1738.     }
  1739.     // }}}
  1740.     /**
  1741.      * Get id
  1742.      *
  1743.      * @return integer
  1744.      */
  1745.     public function getId()
  1746.     {
  1747.         return $this->id;
  1748.     }
  1749.     /**
  1750.      * Set decimals
  1751.      *
  1752.      * @param integer $decimals
  1753.      * @return Attribute
  1754.      */
  1755.     public function setDecimals($decimals)
  1756.     {
  1757.         $this->decimals $decimals;
  1758.         return $this;
  1759.     }
  1760.     /**
  1761.      * Get decimals
  1762.      *
  1763.      * @return integer
  1764.      */
  1765.     public function getDecimals()
  1766.     {
  1767.         return $this->decimals;
  1768.     }
  1769.     /**
  1770.      * Get type
  1771.      *
  1772.      * @return string
  1773.      */
  1774.     public function getType()
  1775.     {
  1776.         return $this->type;
  1777.     }
  1778.     /**
  1779.      * Get display mode
  1780.      *
  1781.      * @param \XLite\Model\Product $product Product OPTIONAL
  1782.      * @return string
  1783.      */
  1784.     public function getDisplayMode($product null)
  1785.     {
  1786.         $productId $product
  1787.             $product->getId()
  1788.             : \XLite\Core\Request::getInstance()->product_id;
  1789.         $prop $this->getProductAttributeProperty($productId);
  1790.         if ($prop && $prop->getDisplayMode()) {
  1791.             return $prop->getDisplayMode();
  1792.         }
  1793.         return $this->displayMode;
  1794.     }
  1795.     /**
  1796.      * @param $productId
  1797.      *
  1798.      * @return null|\XLite\Model\AttributeProperty
  1799.      */
  1800.     protected function getProductAttributeProperty($productId)
  1801.     {
  1802.         return $this->executeCachedRuntime(function () use ($productId) {
  1803.             $property null;
  1804.             if (
  1805.                 $productId
  1806.                 && ($product Database::getRepo(\XLite\Model\Product::class)->find($productId))
  1807.             ) {
  1808.                 $property Database::getRepo(\XLite\Model\AttributeProperty::class)->findOneBy([
  1809.                     'product' => $product,
  1810.                     'attribute'  => $this,
  1811.                 ]);
  1812.             }
  1813.             return $property;
  1814.         }, ['getProductAttributeProperty'$this->getId(), $productId]);
  1815.     }
  1816.     /**
  1817.      * Set display mode
  1818.      *
  1819.      * @param string $value
  1820.      * @param boolean $isNew New attribute flag OPTIONAL
  1821.      *
  1822.      * @return Attribute
  1823.      */
  1824.     public function setDisplayMode($value$isNew false)
  1825.     {
  1826.         if (
  1827.             $this->displayMode !== $value
  1828.             && $this->getAttributeProperties()
  1829.             && (!\XLite\Core\Request::getInstance()->product_id
  1830.                 || $isNew)
  1831.         ) {
  1832.             foreach ($this->getAttributeProperties() as $prop) {
  1833.                 $prop->setDisplayMode($value);
  1834.             }
  1835.         }
  1836.         $this->displayMode $value;
  1837.         return $this;
  1838.     }
  1839.     /**
  1840.      * Return display modes
  1841.      *
  1842.      * @return array
  1843.      */
  1844.     public static function getDisplayModes()
  1845.     {
  1846.         return [
  1847.             static::SELECT_BOX_MODE    => static::t('Selectbox'),
  1848.             static::BLOCKS_MODE        => static::t('Blocks'),
  1849.             static::SPECIFICATION_MODE => static::t('Specification'),
  1850.         ];
  1851.     }
  1852.     /**
  1853.      * Return display mode name
  1854.      *
  1855.      * @return string
  1856.      */
  1857.     public function getDisplayModeName()
  1858.     {
  1859.         $displayModes self::getDisplayModes();
  1860.         return $displayModes[$this->displayMode] ?? '';
  1861.     }
  1862.     /**
  1863.      * Set productClass
  1864.      *
  1865.      * @param \XLite\Model\ProductClass $productClass
  1866.      * @return Attribute
  1867.      */
  1868.     public function setProductClass(\XLite\Model\ProductClass $productClass null)
  1869.     {
  1870.         $this->productClass $productClass;
  1871.         return $this;
  1872.     }
  1873.     /**
  1874.      * Get productClass
  1875.      *
  1876.      * @return \XLite\Model\ProductClass
  1877.      */
  1878.     public function getProductClass()
  1879.     {
  1880.         return $this->productClass;
  1881.     }
  1882.     /**
  1883.      * Set attributeGroup
  1884.      *
  1885.      * @param \XLite\Model\AttributeGroup $attributeGroup
  1886.      * @return Attribute
  1887.      */
  1888.     public function setAttributeGroup(\XLite\Model\AttributeGroup $attributeGroup null)
  1889.     {
  1890.         $this->attributeGroup $attributeGroup;
  1891.         return $this;
  1892.     }
  1893.     /**
  1894.      * Get attributeGroup
  1895.      *
  1896.      * @return \XLite\Model\AttributeGroup
  1897.      */
  1898.     public function getAttributeGroup()
  1899.     {
  1900.         return $this->attributeGroup;
  1901.     }
  1902.     /**
  1903.      * Add attribute_options
  1904.      *
  1905.      * @param \XLite\Model\AttributeOption $attributeOptions
  1906.      * @return Attribute
  1907.      */
  1908.     public function addAttributeOptions(\XLite\Model\AttributeOption $attributeOptions)
  1909.     {
  1910.         $this->attribute_options[] = $attributeOptions;
  1911.         return $this;
  1912.     }
  1913.     /**
  1914.      * Get attribute_options
  1915.      *
  1916.      * @return \Doctrine\Common\Collections\Collection
  1917.      */
  1918.     public function getAttributeOptions()
  1919.     {
  1920.         return $this->attribute_options;
  1921.     }
  1922.     /**
  1923.      * Set product
  1924.      *
  1925.      * @param \XLite\Model\Product $product
  1926.      * @return Attribute
  1927.      */
  1928.     public function setProduct(\XLite\Model\Product $product null)
  1929.     {
  1930.         $this->product $product;
  1931.         return $this;
  1932.     }
  1933.     /**
  1934.      * Get product
  1935.      *
  1936.      * @return \XLite\Model\Product
  1937.      */
  1938.     public function getProduct()
  1939.     {
  1940.         return $this->product;
  1941.     }
  1942.     /**
  1943.      * Add attribute property
  1944.      *
  1945.      * @param \XLite\Model\AttributeProperty $attributeProperty
  1946.      * @return Attribute
  1947.      */
  1948.     public function addAttributeProperty(\XLite\Model\AttributeProperty $attributeProperty)
  1949.     {
  1950.         $this->attribute_properties[] = $attributeProperty;
  1951.         return $this;
  1952.     }
  1953.     /**
  1954.      * Get attribute_properties
  1955.      *
  1956.      * @return \Doctrine\Common\Collections\Collection
  1957.      */
  1958.     public function getAttributeProperties()
  1959.     {
  1960.         return $this->attribute_properties;
  1961.     }
  1962.     // {{{ Translation Getters / setters
  1963.     /**
  1964.      * @return string
  1965.      */
  1966.     public function getUnit()
  1967.     {
  1968.         return $this->getTranslationField(__FUNCTION__);
  1969.     }
  1970.     /**
  1971.      * @param string $unit
  1972.      *
  1973.      * @return \XLite\Model\Base\Translation
  1974.      */
  1975.     public function setUnit($unit)
  1976.     {
  1977.         return $this->setTranslationField(__FUNCTION__$unit);
  1978.     }
  1979.     // }}}
  1980. }