123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Eav\Model\ResourceModel\Entity;
- use Magento\Eav\Model\Config;
- use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
- use Magento\Eav\Model\Entity\Attribute as EntityAttribute;
- use Magento\Framework\App\ObjectManager;
- use Magento\Framework\DB\Select;
- use Magento\Framework\Model\AbstractModel;
- /**
- * EAV attribute resource model
- *
- * @api
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- * @since 100.0.2
- */
- class Attribute extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
- {
- /**
- * Eav Entity attributes cache
- *
- * @var array
- */
- protected static $_entityAttributes = [];
- /**
- * @var \Magento\Store\Model\StoreManagerInterface
- */
- protected $_storeManager;
- /**
- * @var Type
- */
- protected $_eavEntityType;
- /**
- * @var Config
- */
- private $config;
- /**
- * Class constructor
- *
- * @param \Magento\Framework\Model\ResourceModel\Db\Context $context
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param Type $eavEntityType
- * @param string $connectionName
- * @codeCoverageIgnore
- */
- public function __construct(
- \Magento\Framework\Model\ResourceModel\Db\Context $context,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- Type $eavEntityType,
- $connectionName = null
- ) {
- $this->_storeManager = $storeManager;
- $this->_eavEntityType = $eavEntityType;
- parent::__construct($context, $connectionName);
- }
- /**
- * Define main table
- *
- * @return void
- * @codeCoverageIgnore
- */
- protected function _construct()
- {
- $this->_init('eav_attribute', 'attribute_id');
- }
- /**
- * Initialize unique fields
- *
- * @return $this
- * @codeCoverageIgnore
- */
- protected function _initUniqueFields()
- {
- $this->_uniqueFields = [
- ['field' => ['attribute_code', 'entity_type_id'], 'title' => __('Attribute with the same code')],
- ];
- return $this;
- }
- /**
- * Load attribute data by attribute code
- *
- * @param EntityAttribute|\Magento\Framework\Model\AbstractModel $object
- * @param int $entityTypeId
- * @param string $code
- * @return bool
- */
- public function loadByCode(AbstractModel $object, $entityTypeId, $code)
- {
- $bind = [':entity_type_id' => $entityTypeId];
- $select = $this->_getLoadSelect('attribute_code', $code, $object)->where('entity_type_id = :entity_type_id');
- $data = $this->getConnection()->fetchRow($select, $bind);
- if ($data) {
- $object->setData($data);
- $this->_afterLoad($object);
- return true;
- }
- return false;
- }
- /**
- * Retrieve Max Sort order for attribute in group
- *
- * @param AbstractModel $object
- * @return int
- */
- private function _getMaxSortOrder(AbstractModel $object)
- {
- if ((int)$object->getAttributeGroupId() > 0) {
- $connection = $this->getConnection();
- $bind = [
- ':attribute_set_id' => $object->getAttributeSetId(),
- ':attribute_group_id' => $object->getAttributeGroupId(),
- ];
- $select = $connection->select()->from(
- $this->getTable('eav_entity_attribute'),
- new \Zend_Db_Expr("MAX(sort_order)")
- )->where(
- 'attribute_set_id = :attribute_set_id'
- )->where(
- 'attribute_group_id = :attribute_group_id'
- );
- return $connection->fetchOne($select, $bind);
- }
- return 0;
- }
- /**
- * Delete entity
- *
- * @param \Magento\Framework\Model\AbstractMode $object
- * @return $this
- */
- public function deleteEntity(\Magento\Framework\Model\AbstractModel $object)
- {
- if (!$object->getEntityAttributeId()) {
- return $this;
- }
- $this->getConnection()->delete(
- $this->getTable('eav_entity_attribute'),
- ['entity_attribute_id = ?' => $object->getEntityAttributeId()]
- );
- return $this;
- }
- /**
- * Validate attribute data before save
- *
- * @param EntityAttribute|AbstractModel $object
- * @return $this
- * @throws \Magento\Framework\Exception\LocalizedException
- */
- protected function _beforeSave(AbstractModel $object)
- {
- $frontendLabel = $object->getFrontendLabel();
- if (is_array($frontendLabel)) {
- $this->checkDefaultFrontendLabelExists($frontendLabel, $frontendLabel);
- $object->setFrontendLabel($frontendLabel[0])->setStoreLabels($frontendLabel);
- } else {
- $this->setStoreLabels($object, $frontendLabel);
- }
- /**
- * @todo need use default source model of entity type !!!
- */
- if (!$object->getId()) {
- if ($object->getFrontendInput() == 'select') {
- $object->setSourceModel(\Magento\Eav\Model\Entity\Attribute\Source\Table::class);
- }
- }
- return parent::_beforeSave($object);
- }
- /**
- * Save additional attribute data after save attribute
- *
- * @param EntityAttribute|AbstractModel $object
- * @return $this
- */
- protected function _afterSave(AbstractModel $object)
- {
- $this->_saveStoreLabels(
- $object
- )->_saveAdditionalAttributeData(
- $object
- )->saveInSetIncluding(
- $object
- )->_saveOption(
- $object
- );
- $this->getConfig()->clear();
- return parent::_afterSave($object);
- }
- /**
- * Perform actions after object delete
- *
- * @param \Magento\Framework\Model\AbstractModel|\Magento\Framework\DataObject $object
- * @return $this
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
- * @since 100.0.7
- */
- protected function _afterDelete(\Magento\Framework\Model\AbstractModel $object)
- {
- $this->getConfig()->clear();
- return $this;
- }
- /**
- * Returns config instance
- *
- * @return Config
- * @deprecated 100.0.7
- */
- private function getConfig()
- {
- if (!$this->config) {
- $this->config = ObjectManager::getInstance()->get(Config::class);
- }
- return $this->config;
- }
- /**
- * Save store labels
- *
- * @param EntityAttribute|\Magento\Framework\Model\AbstractModel $object
- * @return $this
- */
- protected function _saveStoreLabels(AbstractModel $object)
- {
- $storeLabels = $object->getStoreLabels();
- if (is_array($storeLabels)) {
- $connection = $this->getConnection();
- if ($object->getId()) {
- $condition = ['attribute_id =?' => $object->getId()];
- $connection->delete($this->getTable('eav_attribute_label'), $condition);
- }
- foreach ($storeLabels as $storeId => $label) {
- if ($storeId == 0 || !strlen($label)) {
- continue;
- }
- $bind = ['attribute_id' => $object->getId(), 'store_id' => $storeId, 'value' => $label];
- $connection->insert($this->getTable('eav_attribute_label'), $bind);
- }
- }
- return $this;
- }
- /**
- * Save additional data of attribute
- *
- * @param EntityAttribute|\Magento\Framework\Model\AbstractModel $object
- * @return $this
- */
- protected function _saveAdditionalAttributeData(AbstractModel $object)
- {
- $additionalTable = $this->getAdditionalAttributeTable($object->getEntityTypeId());
- if ($additionalTable) {
- $connection = $this->getConnection();
- $data = $this->_prepareDataForTable($object, $this->getTable($additionalTable));
- $bind = [':attribute_id' => $object->getId()];
- $select = $connection->select()->from(
- $this->getTable($additionalTable),
- ['attribute_id']
- )->where(
- 'attribute_id = :attribute_id'
- );
- $result = $connection->fetchOne($select, $bind);
- if ($result) {
- $where = ['attribute_id = ?' => $object->getId()];
- $connection->update($this->getTable($additionalTable), $data, $where);
- } else {
- $connection->insert($this->getTable($additionalTable), $data);
- }
- }
- return $this;
- }
- /**
- * Save in set including
- *
- * @param AbstractModel $object
- * @param int|null $attributeEntityId
- * @param int|null $attributeSetId
- * @param int|null $attributeGroupId
- * @param int|null $attributeSortOrder
- * @return $this
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- public function saveInSetIncluding(
- AbstractModel $object,
- $attributeEntityId = null,
- $attributeSetId = null,
- $attributeGroupId = null,
- $attributeSortOrder = null
- ) {
- $attributeId = $attributeEntityId === null ? (int)$object->getId() : (int)$attributeEntityId;
- $setId = $attributeSetId === null ? (int)$object->getAttributeSetId() : (int)$attributeSetId;
- $groupId = $attributeGroupId === null ? (int)$object->getAttributeGroupId() : (int)$attributeGroupId;
- $attributeSortOrder = $attributeSortOrder === null ? (int)$object->getSortOrder() : (int)$attributeSortOrder;
- if ($setId && $groupId && $object->getEntityTypeId()) {
- $connection = $this->getConnection();
- $table = $this->getTable('eav_entity_attribute');
- $sortOrder = $attributeSortOrder ?: $this->_getMaxSortOrder($object) + 1;
- $data = [
- 'entity_type_id' => $object->getEntityTypeId(),
- 'attribute_set_id' => $setId,
- 'attribute_group_id' => $groupId,
- 'attribute_id' => $attributeId,
- 'sort_order' => $sortOrder,
- ];
- $where = ['attribute_id =?' => $attributeId, 'attribute_set_id =?' => $setId];
- $connection->delete($table, $where);
- $connection->insert($table, $data);
- }
- return $this;
- }
- /**
- * Save attribute options
- *
- * @param EntityAttribute|AbstractModel $object
- * @return $this
- */
- protected function _saveOption(AbstractModel $object)
- {
- $option = $object->getOption();
- if (!is_array($option)) {
- return $this;
- }
- $defaultValue = $object->getDefault() ?: [];
- if (isset($option['value'])) {
- if (!is_array($object->getDefault())) {
- $object->setDefault([]);
- }
- $defaultValue = $this->_processAttributeOptions($object, $option);
- }
- $this->_saveDefaultValue($object, $defaultValue);
- return $this;
- }
- /**
- * Save changes of attribute options, return obtained default value
- *
- * @param EntityAttribute|AbstractModel $object
- * @param array $option
- * @return array
- */
- protected function _processAttributeOptions($object, $option)
- {
- $defaultValue = [];
- foreach ($option['value'] as $optionId => $values) {
- $intOptionId = $this->_updateAttributeOption($object, $optionId, $option);
- if ($intOptionId === false) {
- continue;
- }
- $this->_updateDefaultValue($object, $optionId, $intOptionId, $defaultValue);
- $this->_checkDefaultOptionValue($values);
- $this->_updateAttributeOptionValues($intOptionId, $values);
- }
- return $defaultValue;
- }
- /**
- * Check default option value presence
- *
- * @param array $values
- * @return void
- * @throws \Magento\Framework\Exception\LocalizedException
- */
- protected function _checkDefaultOptionValue($values)
- {
- if (!isset($values[0])) {
- throw new \Magento\Framework\Exception\LocalizedException(
- __("The default option isn't defined. Set the option and try again.")
- );
- }
- }
- /**
- * Update attribute default value
- *
- * @param EntityAttribute|AbstractModel $object
- * @param int|string $optionId
- * @param int $intOptionId
- * @param array $defaultValue
- * @return void
- */
- protected function _updateDefaultValue($object, $optionId, $intOptionId, &$defaultValue)
- {
- if (in_array($optionId, $object->getDefault())) {
- $frontendInput = $object->getFrontendInput();
- if ($frontendInput === 'multiselect') {
- $defaultValue[] = $intOptionId;
- } elseif ($frontendInput === 'select') {
- $defaultValue = [$intOptionId];
- }
- }
- }
- /**
- * Save attribute default value
- *
- * @param AbstractModel $object
- * @param array $defaultValue
- * @return void
- */
- protected function _saveDefaultValue($object, $defaultValue)
- {
- if ($defaultValue !== null) {
- $bind = ['default_value' => implode(',', $defaultValue)];
- $where = ['attribute_id = ?' => $object->getId()];
- $this->getConnection()->update($this->getMainTable(), $bind, $where);
- }
- }
- /**
- * Save option records
- *
- * @param AbstractModel $object
- * @param int $optionId
- * @param array $option
- * @return int|bool
- */
- protected function _updateAttributeOption($object, $optionId, $option)
- {
- $connection = $this->getConnection();
- $table = $this->getTable('eav_attribute_option');
- // ignore strings that start with a number
- $intOptionId = is_numeric($optionId) ? (int)$optionId : 0;
- if (!empty($option['delete'][$optionId])) {
- if ($intOptionId) {
- $connection->delete($table, ['option_id = ?' => $intOptionId]);
- }
- return false;
- }
- $sortOrder = empty($option['order'][$optionId]) ? 0 : $option['order'][$optionId];
- if (!$intOptionId) {
- $data = ['attribute_id' => $object->getId(), 'sort_order' => $sortOrder];
- $connection->insert($table, $data);
- $intOptionId = $connection->lastInsertId($table);
- } else {
- $data = ['sort_order' => $sortOrder];
- $where = ['option_id = ?' => $intOptionId];
- $connection->update($table, $data, $where);
- }
- return $intOptionId;
- }
- /**
- * Save option values records per store
- *
- * @param int $optionId
- * @param array $values
- * @return void
- */
- protected function _updateAttributeOptionValues($optionId, $values)
- {
- $connection = $this->getConnection();
- $table = $this->getTable('eav_attribute_option_value');
- $connection->delete($table, ['option_id = ?' => $optionId]);
- $stores = $this->_storeManager->getStores(true);
- foreach ($stores as $store) {
- $storeId = $store->getId();
- if (!empty($values[$storeId]) || isset($values[$storeId]) && $values[$storeId] == '0') {
- $data = ['option_id' => $optionId, 'store_id' => $storeId, 'value' => $values[$storeId]];
- $connection->insert($table, $data);
- }
- }
- }
- /**
- * Retrieve attribute id by entity type code and attribute code
- *
- * @param string $entityType
- * @param string $code
- * @return int
- */
- public function getIdByCode($entityType, $code)
- {
- $connection = $this->getConnection();
- $bind = [':entity_type_code' => $entityType, ':attribute_code' => $code];
- $select = $connection->select()->from(
- ['a' => $this->getTable('eav_attribute')],
- ['a.attribute_id']
- )->join(
- ['t' => $this->getTable('eav_entity_type')],
- 'a.entity_type_id = t.entity_type_id',
- []
- )->where(
- 't.entity_type_code = :entity_type_code'
- )->where(
- 'a.attribute_code = :attribute_code'
- );
- return $connection->fetchOne($select, $bind);
- }
- /**
- * Get entity attribute
- *
- * @param int|string $entityAttributeId
- * @return array
- * @since 100.1.0
- */
- public function getEntityAttribute($entityAttributeId)
- {
- $select = $this->getConnection()->select()->from(
- $this->getTable('eav_entity_attribute')
- )->where(
- 'entity_attribute_id = ?',
- (int)$entityAttributeId
- );
- return $this->getConnection()->fetchRow($select);
- }
- /**
- * Retrieve attribute codes by front-end type
- *
- * @param string $frontendType
- * @return array
- */
- public function getAttributeCodesByFrontendType($frontendType)
- {
- $connection = $this->getConnection();
- $bind = [':frontend_input' => $frontendType];
- $select = $connection->select()->from(
- $this->getTable('eav_attribute'),
- 'attribute_code'
- )->where(
- 'frontend_input = :frontend_input'
- );
- return $connection->fetchCol($select, $bind);
- }
- /**
- * Retrieve Select For Flat Attribute update
- *
- * @param AbstractAttribute $attribute
- * @param int $storeId
- * @return Select
- */
- public function getFlatUpdateSelect(AbstractAttribute $attribute, $storeId)
- {
- $connection = $this->getConnection();
- $joinConditionTemplate = "%s.entity_id=%s.entity_id" .
- " AND %s.entity_type_id = " .
- $attribute->getEntityTypeId() .
- " AND %s.attribute_id = " .
- $attribute->getId() .
- " AND %s.store_id = %d";
- $joinCondition = sprintf(
- $joinConditionTemplate,
- 'e',
- 't1',
- 't1',
- 't1',
- 't1',
- \Magento\Store\Model\Store::DEFAULT_STORE_ID
- );
- if ($attribute->getFlatAddChildData()) {
- $joinCondition .= ' AND e.child_id = t1.entity_id';
- }
- $valueExpr = $connection->getCheckSql('t2.value_id > 0', 't2.value', 't1.value');
- /** @var $select Select */
- $select = $connection->select()->joinLeft(
- ['t1' => $attribute->getBackend()->getTable()],
- $joinCondition,
- []
- )->joinLeft(
- ['t2' => $attribute->getBackend()->getTable()],
- sprintf($joinConditionTemplate, 't1', 't2', 't2', 't2', 't2', $storeId),
- [$attribute->getAttributeCode() => $valueExpr]
- );
- if ($attribute->getFlatAddChildData()) {
- $select->where("e.is_child = ?", 0);
- }
- return $select;
- }
- /**
- * Returns the column descriptions for a table
- *
- * @param string $table
- * @return array
- * @codeCoverageIgnore
- */
- public function describeTable($table)
- {
- return $this->getConnection()->describeTable($table);
- }
- /**
- * Retrieve additional attribute table name for specified entity type
- *
- * @param int $entityTypeId
- * @return string
- * @codeCoverageIgnore
- */
- public function getAdditionalAttributeTable($entityTypeId)
- {
- return $this->_eavEntityType->getAdditionalAttributeTable($entityTypeId);
- }
- /**
- * Load additional attribute data.
- *
- * Load label of current active store
- *
- * @param EntityAttribute|AbstractModel $object
- * @return $this
- */
- protected function _afterLoad(AbstractModel $object)
- {
- /** @var $entityType \Magento\Eav\Model\Entity\Type */
- $entityType = $object->getData('entity_type');
- if ($entityType) {
- $additionalTable = $entityType->getAdditionalAttributeTable();
- } else {
- $additionalTable = $this->getAdditionalAttributeTable($object->getEntityTypeId());
- }
- if ($additionalTable) {
- $connection = $this->getConnection();
- $bind = [':attribute_id' => $object->getId()];
- $select = $connection->select()->from(
- $this->getTable($additionalTable)
- )->where(
- 'attribute_id = :attribute_id'
- );
- $result = $connection->fetchRow($select, $bind);
- if ($result) {
- $object->addData($result);
- }
- }
- return $this;
- }
- /**
- * @var array
- */
- private $storeLabelsCache = [];
- /**
- * Retrieve store labels by given attribute id
- *
- * @param int $attributeId
- * @return array
- */
- public function getStoreLabelsByAttributeId($attributeId)
- {
- if (!isset($this->storeLabelsCache[$attributeId])) {
- $connection = $this->getConnection();
- $bind = [':attribute_id' => $attributeId];
- $select = $connection->select()->from(
- $this->getTable('eav_attribute_label'),
- ['store_id', 'value']
- )->where(
- 'attribute_id = :attribute_id'
- );
- $this->storeLabelsCache[$attributeId] = $connection->fetchPairs($select, $bind);
- }
- return $this->storeLabelsCache[$attributeId];
- }
- /**
- * Load by given attributes ids and return only exist attribute ids
- *
- * @param array $attributeIds
- * @return array
- */
- public function getValidAttributeIds($attributeIds)
- {
- $connection = $this->getConnection();
- $select = $connection->select()->from(
- $this->getMainTable(),
- ['attribute_id']
- )->where(
- 'attribute_id IN (?)',
- $attributeIds
- );
- return $connection->fetchCol($select);
- }
- /**
- * Provide variables to serialize
- *
- * @return array
- * @since 100.0.7
- */
- public function __sleep()
- {
- $properties = parent::__sleep();
- $properties = array_diff($properties, ['_storeManager']);
- return $properties;
- }
- /**
- * Restore global dependencies
- *
- * @return void
- * @since 100.0.7
- */
- public function __wakeup()
- {
- parent::__wakeup();
- $this->_storeManager = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Store\Model\StoreManagerInterface::class);
- }
- /**
- * This method extracts frontend labels into array and sets array values as storeLabels into an object.
- *
- * @param AbstractModel $object
- * @param string|null $frontendLabel
- * @return void
- * @throws \Magento\Framework\Exception\LocalizedException
- */
- private function setStoreLabels(AbstractModel $object, $frontendLabel)
- {
- $resultLabel = [];
- $frontendLabels = $object->getFrontendLabels();
- if (isset($frontendLabels[0])
- && $frontendLabels[0] instanceof \Magento\Eav\Model\Entity\Attribute\FrontendLabel
- ) {
- foreach ($frontendLabels as $label) {
- $resultLabel[$label->getStoreId()] = $label->getLabel();
- }
- $this->checkDefaultFrontendLabelExists($frontendLabel, $resultLabel);
- $object->setStoreLabels($resultLabel);
- }
- }
- /**
- * This method checks whether value for default frontend label exists in attribute data.
- *
- * @param array|string|null $frontendLabel
- * @param array $resultLabels
- * @return void
- * @throws \Magento\Framework\Exception\LocalizedException
- */
- private function checkDefaultFrontendLabelExists($frontendLabel, $resultLabels)
- {
- $isAdminStoreLabel = (isset($resultLabels[0]) && !empty($resultLabels[0]));
- if (empty($frontendLabel) && !$isAdminStoreLabel) {
- throw new \Magento\Framework\Exception\LocalizedException(__('The storefront label is not defined.'));
- }
- }
- }
|