classes/XLite/Model/Attribute.php line 687

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