├── .eslintignore ├── .eslintrc ├── Api ├── AttributeRepositoryInterface.php └── Data │ ├── SellerAttributeInterface.php │ └── SellerInterface.php ├── ISSUE_TEMPLATE.md ├── Model ├── Locator │ ├── LocatorInterface.php │ └── RegistryLocator.php ├── ResourceModel │ ├── AttributePersistor.php │ ├── Seller.php │ └── Seller │ │ ├── Attribute.php │ │ ├── Attribute │ │ └── Collection.php │ │ └── Collection.php ├── Seller.php ├── Seller │ ├── Attribute │ │ ├── Repository.php │ │ └── ScopeOverriddenValue.php │ └── DataProvider.php ├── SellerMediaUpload.php └── SellerRepository.php ├── Setup └── Patch │ ├── Data │ └── DefaultSellerAttributes.php │ └── SellerSetup.php ├── Ui └── Component │ └── Seller │ ├── Form │ ├── DataProvider.php │ ├── FieldMapper.php │ └── Modifier │ │ └── Eav.php │ └── Listing │ ├── AddStoreFieldToCollection.php │ └── DataProvider.php ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ ├── di.xml │ └── menu.xml ├── db_schema.xml ├── db_schema_whitelist.json ├── di.xml ├── events.xml └── module.xml ├── lib └── web │ └── fonts │ └── retailersuite │ ├── retailersuite.eot │ ├── retailersuite.svg │ ├── retailersuite.ttf │ ├── retailersuite.woff │ └── retailersuite.woff2 ├── registration.php └── view └── adminhtml └── web └── css └── source └── _module.less /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*{.,-}min.js 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | ecmaFeatures: 2 | modules: true 3 | jsx: true 4 | 5 | env: 6 | amd: true 7 | browser: true 8 | es6: true 9 | jquery: true 10 | node: true 11 | 12 | # http://eslint.org/docs/rules/ 13 | rules: 14 | # Possible Errors 15 | comma-dangle: [2, never] 16 | no-cond-assign: 2 17 | no-console: 0 18 | no-constant-condition: 2 19 | no-control-regex: 2 20 | no-debugger: 2 21 | no-dupe-args: 2 22 | no-dupe-keys: 2 23 | no-duplicate-case: 2 24 | no-empty: 2 25 | no-empty-character-class: 2 26 | no-ex-assign: 2 27 | no-extra-boolean-cast: 2 28 | no-extra-parens: 0 29 | no-extra-semi: 2 30 | no-func-assign: 2 31 | no-inner-declarations: [2, functions] 32 | no-invalid-regexp: 2 33 | no-irregular-whitespace: 2 34 | no-negated-in-lhs: 2 35 | no-obj-calls: 2 36 | no-regex-spaces: 2 37 | no-sparse-arrays: 2 38 | no-unexpected-multiline: 2 39 | no-unreachable: 2 40 | use-isnan: 2 41 | valid-jsdoc: 0 42 | valid-typeof: 2 43 | 44 | # Best Practices 45 | accessor-pairs: 2 46 | block-scoped-var: 0 47 | complexity: [2, 6] 48 | consistent-return: 0 49 | curly: 0 50 | default-case: 0 51 | dot-location: 0 52 | dot-notation: 0 53 | eqeqeq: 2 54 | guard-for-in: 2 55 | no-alert: 2 56 | no-caller: 2 57 | no-case-declarations: 2 58 | no-div-regex: 2 59 | no-else-return: 0 60 | no-empty-label: 2 61 | no-empty-pattern: 2 62 | no-eq-null: 2 63 | no-eval: 2 64 | no-extend-native: 2 65 | no-extra-bind: 2 66 | no-fallthrough: 2 67 | no-floating-decimal: 0 68 | no-implicit-coercion: 0 69 | no-implied-eval: 2 70 | no-invalid-this: 0 71 | no-iterator: 2 72 | no-labels: 0 73 | no-lone-blocks: 2 74 | no-loop-func: 2 75 | no-magic-number: 0 76 | no-multi-spaces: 0 77 | no-multi-str: 0 78 | no-native-reassign: 2 79 | no-new-func: 2 80 | no-new-wrappers: 2 81 | no-new: 2 82 | no-octal-escape: 2 83 | no-octal: 2 84 | no-proto: 2 85 | no-redeclare: 2 86 | no-return-assign: 2 87 | no-script-url: 2 88 | no-self-compare: 2 89 | no-sequences: 0 90 | no-throw-literal: 0 91 | no-unused-expressions: 2 92 | no-useless-call: 2 93 | no-useless-concat: 2 94 | no-void: 2 95 | no-warning-comments: 0 96 | no-with: 2 97 | radix: 2 98 | vars-on-top: 0 99 | wrap-iife: 2 100 | yoda: 0 101 | 102 | # Strict 103 | strict: 0 104 | 105 | # Variables 106 | init-declarations: 0 107 | no-catch-shadow: 2 108 | no-delete-var: 2 109 | no-label-var: 2 110 | no-shadow-restricted-names: 2 111 | no-shadow: 0 112 | no-undef-init: 2 113 | no-undef: 0 114 | no-undefined: 0 115 | no-unused-vars: 0 116 | no-use-before-define: 0 117 | 118 | # Node.js and CommonJS 119 | callback-return: 2 120 | global-require: 2 121 | handle-callback-err: 2 122 | no-mixed-requires: 0 123 | no-new-require: 0 124 | no-path-concat: 2 125 | no-process-exit: 2 126 | no-restricted-modules: 0 127 | no-sync: 0 128 | 129 | # Stylistic Issues 130 | array-bracket-spacing: 0 131 | block-spacing: 0 132 | brace-style: 0 133 | camelcase: 0 134 | comma-spacing: 0 135 | comma-style: 0 136 | computed-property-spacing: 0 137 | consistent-this: 0 138 | eol-last: 0 139 | func-names: 0 140 | func-style: 0 141 | id-length: 0 142 | id-match: 0 143 | indent: 0 144 | jsx-quotes: 0 145 | key-spacing: 0 146 | linebreak-style: 0 147 | lines-around-comment: 0 148 | max-depth: 0 149 | max-len: 0 150 | max-nested-callbacks: 0 151 | max-params: 0 152 | max-statements: [2, 30] 153 | new-cap: 0 154 | new-parens: 0 155 | newline-after-var: 0 156 | no-array-constructor: 0 157 | no-bitwise: 0 158 | no-continue: 0 159 | no-inline-comments: 0 160 | no-lonely-if: 0 161 | no-mixed-spaces-and-tabs: 0 162 | no-multiple-empty-lines: 0 163 | no-negated-condition: 0 164 | no-nested-ternary: 0 165 | no-new-object: 0 166 | no-plusplus: 0 167 | no-restricted-syntax: 0 168 | no-spaced-func: 0 169 | no-ternary: 0 170 | no-trailing-spaces: 0 171 | no-underscore-dangle: 0 172 | no-unneeded-ternary: 0 173 | object-curly-spacing: 0 174 | one-var: 0 175 | operator-assignment: 0 176 | operator-linebreak: 0 177 | padded-blocks: 0 178 | quote-props: 0 179 | quotes: 0 180 | require-jsdoc: 0 181 | semi-spacing: 0 182 | semi: 0 183 | sort-vars: 0 184 | space-after-keywords: 0 185 | space-before-blocks: 0 186 | space-before-function-paren: 0 187 | space-before-keywords: 0 188 | space-in-parens: 0 189 | space-infix-ops: 0 190 | space-return-throw-case: 0 191 | space-unary-ops: 0 192 | spaced-comment: 0 193 | wrap-regex: 0 194 | 195 | # ECMAScript 6 196 | arrow-body-style: 0 197 | arrow-parens: 0 198 | arrow-spacing: 0 199 | constructor-super: 0 200 | generator-star-spacing: 0 201 | no-arrow-condition: 0 202 | no-class-assign: 0 203 | no-const-assign: 0 204 | no-dupe-class-members: 0 205 | no-this-before-super: 0 206 | no-var: 0 207 | object-shorthand: 0 208 | prefer-arrow-callback: 0 209 | prefer-const: 0 210 | prefer-reflect: 0 211 | prefer-spread: 0 212 | prefer-template: 0 213 | require-yield: 0 214 | -------------------------------------------------------------------------------- /Api/AttributeRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Preconditions 4 | 5 | 6 | 7 | Magento Version : 8 | 9 | 10 | Module Seller Version : 11 | 12 | 13 | Environment : 14 | 15 | 16 | Third party modules : 17 | 18 | ### Steps to reproduce 19 | 20 | 1. 21 | 2. 22 | 3. 23 | 24 | ### Expected result 25 | 26 | 1. 27 | 28 | ### Actual result 29 | 30 | 1. [Screenshot, logs] 31 | 32 | 33 | -------------------------------------------------------------------------------- /Model/Locator/LocatorInterface.php: -------------------------------------------------------------------------------- 1 | seller) { 33 | return $this->seller; 34 | } 35 | 36 | if ($this->registry->registry('current_seller')) { 37 | return $this->seller = $this->registry->registry('current_seller'); 38 | } 39 | 40 | return null; 41 | } 42 | /** 43 | * @inheritdoc 44 | */ 45 | public function getStore(): ?StoreInterface 46 | { 47 | if (null !== $this->store) { 48 | return $this->store; 49 | } 50 | 51 | if ($this->getSeller() && $this->getSeller()->getData('store_id')) { 52 | $this->store = $this->storeManager->getStore($this->getSeller()->getData('store_id')); 53 | 54 | return $this->store; 55 | } 56 | 57 | if ($this->registry->registry('current_store') !== null) { 58 | $this->store = $this->registry->registry('current_store'); 59 | 60 | return $this->store; 61 | } 62 | 63 | return $this->storeManager->getStore(Store::DEFAULT_STORE_ID); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Model/ResourceModel/AttributePersistor.php: -------------------------------------------------------------------------------- 1 | isScopeGlobal(); 23 | } 24 | 25 | return parent::getScopeValue($scope, $attribute, $useDefault); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Model/ResourceModel/Seller.php: -------------------------------------------------------------------------------- 1 | _type)) { 46 | $this->setType(SellerInterface::ENTITY); 47 | } 48 | 49 | return parent::getEntityType(); 50 | } 51 | 52 | /** 53 | * Set store Id. 54 | */ 55 | public function setStoreId(int $storeId): self 56 | { 57 | $this->storeId = $storeId; 58 | 59 | return $this; 60 | } 61 | 62 | /** 63 | * Return store id. 64 | */ 65 | public function getStoreId(): int 66 | { 67 | if ($this->storeId === null) { 68 | return $this->storeManager->getStore()->getId(); 69 | } 70 | 71 | return $this->storeId; 72 | } 73 | 74 | /** 75 | * Check if seller id exist. 76 | */ 77 | public function checkId(int $entityId): string 78 | { 79 | $select = $this->getConnection()->select()->from( 80 | $this->getEntityTable(), 81 | 'entity_id' 82 | )->where( 83 | 'entity_id = :entity_id' 84 | ); 85 | $bind = ['entity_id' => $entityId]; 86 | 87 | return $this->getConnection()->fetchOne($select, $bind); 88 | } 89 | 90 | /** 91 | * Check array of seller identifiers. 92 | */ 93 | public function verifyIds(array $ids): array 94 | { 95 | if (empty($ids)) { 96 | return []; 97 | } 98 | 99 | $select = $this->getConnection()->select()->from( 100 | $this->getEntityTable(), 101 | 'entity_id' 102 | )->where( 103 | 'entity_id IN(?)', 104 | $ids 105 | ); 106 | 107 | return $this->getConnection()->fetchCol($select); 108 | } 109 | 110 | /** 111 | * Get "is_active" attribute identifier. 112 | */ 113 | public function getIsActiveAttributeId(): int 114 | { 115 | if ($this->isActiveAttributeId === null) { 116 | $this->isActiveAttributeId = (int) $this->_eavConfig 117 | ->getAttribute($this->getEntityType(), 'is_active') 118 | ->getAttributeId(); 119 | } 120 | 121 | return $this->isActiveAttributeId; 122 | } 123 | 124 | /** 125 | * @inheritdoc 126 | */ 127 | public function load($object, $entityId, $attributes = []) 128 | { 129 | // Reset firstly loaded attributes 130 | $this->_attributes = []; 131 | $this->loadAttributesMetadata($attributes); 132 | 133 | $object = $this->entityManager->load($object, (string) $entityId); 134 | 135 | if (!$this->entityManager->has($object)) { 136 | $object->isObjectNew(true); 137 | } 138 | 139 | $this->afterLoad($object); 140 | 141 | return $this; 142 | } 143 | 144 | /** 145 | * @inheritdoc 146 | */ 147 | public function delete($object) 148 | { 149 | $this->beforeDelete($object); 150 | $this->entityManager->delete($object); 151 | $this->afterDelete($object); 152 | 153 | return $this; 154 | } 155 | 156 | /** 157 | * @inheritdoc 158 | */ 159 | public function save(AbstractModel $object) 160 | { 161 | $this->beforeSave($object); 162 | $this->entityManager->save($object); 163 | $this->afterSave($object); 164 | 165 | return $this; 166 | } 167 | 168 | /** 169 | * Retrieve Attribute set data by id or name. 170 | */ 171 | public function getAttributeSetIdByName(?string $attributeSetId): int 172 | { 173 | $select = $this->_resource->getConnection()->select(); 174 | $field = 'attribute_set_name'; 175 | $table = $this->_resource->getTableName("eav_attribute_set"); 176 | 177 | $select->from($table, "attribute_set_id") 178 | ->where($this->getConnection()->prepareSqlCondition("entity_type_id", ['eq' => $this->getTypeId()])) 179 | ->where($this->getConnection()->prepareSqlCondition($field, ['eq' => $attributeSetId])); 180 | 181 | return (int) $this->_resource->getConnection()->fetchOne($select); 182 | } 183 | 184 | /** 185 | * @inheritdoc 186 | */ 187 | public function beforeSave(DataObject $object): void 188 | { 189 | // Enforce loading of all attributes to ensure their beforeSave is correctly processed. 190 | $this->loadAllAttributes($object); 191 | parent::beforeSave($object); 192 | } 193 | 194 | /** 195 | * Get Seller identifier by code. 196 | */ 197 | public function getIdByCode(string $code): int 198 | { 199 | $connection = $this->getConnection(); 200 | $select = $connection->select() 201 | ->from($this->getEntityTable(), 'entity_id') 202 | ->where('seller_code = :seller_code'); 203 | 204 | return (int) $connection->fetchOne($select, [':seller_code' => $code]); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /Model/ResourceModel/Seller/Attribute.php: -------------------------------------------------------------------------------- 1 | getAttributeCode(), $this->globalAttributes); 32 | } 33 | 34 | /** 35 | * Retrieve attribute is website scope website 36 | */ 37 | public function isScopeWebsite(): bool 38 | { 39 | return $this->getIsGlobal() == self::SCOPE_WEBSITE; 40 | } 41 | 42 | /** 43 | * Retrieve attribute is store scope flag 44 | */ 45 | public function isScopeStore(): bool 46 | { 47 | return !$this->isScopeGlobal() && !$this->isScopeWebsite(); 48 | } 49 | 50 | /** 51 | * @inheritdoc 52 | */ 53 | public function __sleep() 54 | { 55 | $this->unsetData('entity_type'); 56 | 57 | return parent::__sleep(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Model/ResourceModel/Seller/Attribute/Collection.php: -------------------------------------------------------------------------------- 1 | _init( 22 | SellerAttribute::class, 23 | Attribute::class 24 | ); 25 | } 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | protected function _initSelect(): self 31 | { 32 | $this->getSelect()->from(['main_table' => $this->getResource()->getMainTable()]) 33 | ->where( 34 | 'main_table.entity_type_id=?', 35 | $this->eavConfig->getEntityType(SellerInterface::ENTITY)->getId() 36 | ); 37 | 38 | return $this; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Model/ResourceModel/Seller/Collection.php: -------------------------------------------------------------------------------- 1 | sellerAttributeSetName = $attributeSetName; 57 | parent::__construct( 58 | $entityFactory, 59 | $logger, 60 | $fetchStrategy, 61 | $eventManager, 62 | $eavConfig, 63 | $resource, 64 | $eavEntityFactory, 65 | $resourceHelper, 66 | $universalFactory, 67 | $connection 68 | ); 69 | } 70 | 71 | /** 72 | * @inheritdoc 73 | */ 74 | protected function _construct() 75 | { 76 | $this->_init(Seller::class, SellerResource::class); 77 | 78 | if ($this->sellerAttributeSetId == null) { 79 | if ($this->sellerAttributeSetName !== null) { 80 | /** @var SellerResource $resource */ 81 | $resource = $this->getResource(); 82 | $this->sellerAttributeSetId = $resource 83 | ->getAttributeSetIdByName($this->sellerAttributeSetName); 84 | } 85 | } 86 | } 87 | 88 | /** 89 | * Set store scope. 90 | */ 91 | public function setStore(Store $store): self 92 | { 93 | $this->setStoreId($this->storeManager->getStore($store)->getId()); 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * Set store scope. 100 | */ 101 | public function setStoreId(mixed $storeId): self 102 | { 103 | if ($storeId instanceof Store) { 104 | $storeId = $storeId->getId(); 105 | } 106 | $this->storeId = (int) $storeId; 107 | 108 | return $this; 109 | } 110 | 111 | /** 112 | * Return current store id. 113 | */ 114 | public function getStoreId(): int 115 | { 116 | if ($this->storeId === null) { 117 | $this->setStoreId($this->storeManager->getStore()->getId()); 118 | } 119 | 120 | return $this->storeId; 121 | } 122 | 123 | /** 124 | * Retrieve default store id. 125 | */ 126 | public function getDefaultStoreId(): int 127 | { 128 | return Store::DEFAULT_STORE_ID; 129 | } 130 | 131 | /** 132 | * @inheritdoc 133 | */ 134 | protected function _initSelect() 135 | { 136 | parent::_initSelect(); 137 | 138 | // Retrieve only sellers of current attribute set if specified. 139 | if ($this->sellerAttributeSetId !== null) { 140 | $this->addFieldToFilter('attribute_set_id', (int) $this->sellerAttributeSetId); 141 | } 142 | 143 | return $this; 144 | } 145 | 146 | /** 147 | * @inheritdoc 148 | */ 149 | protected function _getLoadAttributesSelect($table, $attributeIds = []) 150 | { 151 | if (empty($attributeIds)) { 152 | $attributeIds = $this->_selectAttributes; 153 | } 154 | 155 | $storeId = $this->getStoreId(); 156 | $connection = $this->getConnection(); 157 | 158 | $entityIdField = $this->getEntityPkName($this->getEntity()); 159 | 160 | $select = $this->getBaseAttributesSelect($table, $attributeIds); 161 | 162 | $storeCondition = $this->getDefaultStoreId(); 163 | 164 | if ($storeId) { 165 | $joinCondition = [ 166 | 't_s.attribute_id = t_d.attribute_id', 167 | "t_s.{$entityIdField} = t_d.{$entityIdField}", 168 | $connection->quoteInto('t_s.store_id = ?', $storeId), 169 | ]; 170 | 171 | $select->joinLeft(['t_s' => $table], implode(' AND ', $joinCondition), []); 172 | 173 | $storeCondition = $connection->getIfNullSql('t_s.store_id', Store::DEFAULT_STORE_ID); 174 | } 175 | 176 | $select->where('t_d.store_id = ?', $storeCondition); 177 | 178 | return $select; 179 | } 180 | 181 | /** 182 | * @inheritdoc 183 | * @SuppressWarnings(PHPMD.ExcessiveParameterList) 184 | * @SuppressWarnings(PHPMD.ElseExpression) cf. \Magento\Catalog\Model\ResourceModel\Collection\AbstractCollection 185 | */ 186 | protected function _joinAttributeToSelect($method, $attribute, $tableAlias, $condition, $fieldCode, $fieldAlias) 187 | { 188 | $storeId = $this->getStoreId(); 189 | if (isset($this->_joinAttributes[$fieldCode]['store_id'])) { 190 | $storeId = $this->_joinAttributes[$fieldCode]['store_id']; 191 | } 192 | 193 | $connection = $this->getConnection(); 194 | 195 | if ($storeId != $this->getDefaultStoreId() && !$attribute->isScopeGlobal()) { 196 | /** 197 | * Add joining default value for not default store 198 | * if value for store is null - we use default value 199 | */ 200 | $defCondition = '(' . implode(') AND (', $condition) . ')'; 201 | $defAlias = $tableAlias . '_default'; 202 | $defAlias = $this->getConnection()->getTableName($defAlias); 203 | $defFieldAlias = str_replace($tableAlias, $defAlias, $fieldAlias); 204 | $tableAlias = $this->getConnection()->getTableName($tableAlias); 205 | 206 | $defCondition = str_replace($tableAlias, $defAlias, $defCondition); 207 | $defCondition .= $connection->quoteInto( 208 | // @phpstan-ignore-next-line as $alias null value is default 209 | ' AND ' . $connection->quoteColumnAs("{$defAlias}.store_id", null) . " = ?", 210 | $this->getDefaultStoreId() 211 | ); 212 | 213 | $this->getSelect()->{$method}( 214 | [$defAlias => $attribute->getBackend()->getTable()], 215 | $defCondition, 216 | [] 217 | ); 218 | 219 | $method = 'joinLeft'; 220 | $fieldAlias = $this->getConnection()->getCheckSql( 221 | "{$tableAlias}.value_id > 0", 222 | $fieldAlias, 223 | $defFieldAlias 224 | ); 225 | $this->_joinAttributes[$fieldCode]['condition_alias'] = $fieldAlias; 226 | $this->_joinAttributes[$fieldCode]['attribute'] = $attribute; 227 | } else { 228 | $storeId = $this->getDefaultStoreId(); 229 | } 230 | 231 | $condition[] = $connection->quoteInto( 232 | // @phpstan-ignore-next-line as $alias null value is default 233 | $connection->quoteColumnAs("{$tableAlias}.store_id", null) . ' = ?', 234 | $storeId 235 | ); 236 | 237 | return parent::_joinAttributeToSelect($method, $attribute, $tableAlias, $condition, $fieldCode, $fieldAlias); 238 | } 239 | 240 | /** 241 | * Retrieve Base select for attributes of this collection. 242 | * 243 | * @throws LocalizedException 244 | */ 245 | private function getBaseAttributesSelect(string $table, array $attributeIds = []): Select 246 | { 247 | $connection = $this->getConnection(); 248 | $entityTable = $this->getEntity()->getEntityTable(); 249 | 250 | $entityIdField = $this->getEntityPkName($this->getEntity()); 251 | 252 | return $connection->select()->from( 253 | ['t_d' => $table], 254 | ['attribute_id'] 255 | )->join( 256 | ['e' => $entityTable], 257 | "e.{$entityIdField} = t_d.{$entityIdField}", 258 | ['e.entity_id'] 259 | )->where( 260 | "e.entity_id IN (?)", 261 | array_keys($this->_itemsById) 262 | )->where( 263 | 't_d.attribute_id IN (?)', 264 | $attributeIds 265 | ); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /Model/Seller.php: -------------------------------------------------------------------------------- 1 | _init(SellerResource::class); 79 | } 80 | 81 | /** 82 | * @inheritdoc 83 | */ 84 | public function getName(): string 85 | { 86 | return $this->_getData(self::KEY_NAME); 87 | } 88 | 89 | /** 90 | * @inheritdoc 91 | */ 92 | public function getSellerCode(): string 93 | { 94 | return $this->_getData(self::KEY_SELLER_CODE); 95 | } 96 | 97 | /** 98 | * @inheritdoc 99 | */ 100 | public function getCreatedAt(): ?string 101 | { 102 | return $this->getData('created_at'); 103 | } 104 | 105 | /** 106 | * @inheritdoc 107 | */ 108 | public function getUpdatedAt(): ?string 109 | { 110 | return $this->getData(self::KEY_UPDATED_AT); 111 | } 112 | 113 | /** 114 | * @inheritdoc 115 | */ 116 | public function getIsActive(): bool 117 | { 118 | return (bool) $this->getData(self::KEY_IS_ACTIVE); 119 | } 120 | 121 | /** 122 | * @inheritdoc 123 | */ 124 | public function setName(string $name): self 125 | { 126 | return $this->setData(self::KEY_NAME, $name); 127 | } 128 | 129 | /** 130 | * @inheritdoc 131 | */ 132 | public function setSellerCode(string $sellerCode): self 133 | { 134 | return $this->setData(self::KEY_SELLER_CODE, $sellerCode); 135 | } 136 | 137 | /** 138 | * @inheritdoc 139 | */ 140 | public function setIsActive(bool $isActive): self 141 | { 142 | return $this->setData(self::KEY_IS_ACTIVE, (bool) $isActive); 143 | } 144 | 145 | /** 146 | * @inheritdoc 147 | */ 148 | public function setCreatedAt(string $createdAt): self 149 | { 150 | return $this->setData(self::KEY_CREATED_AT, $createdAt); 151 | } 152 | 153 | /** 154 | * @inheritdoc 155 | */ 156 | public function setUpdatedAt(string $updatedAt): self 157 | { 158 | return $this->setData(self::KEY_UPDATED_AT, $updatedAt); 159 | } 160 | 161 | /** 162 | * Get default attribute source model 163 | */ 164 | public function getDefaultAttributeSourceModel(): string 165 | { 166 | return Table::class; 167 | } 168 | 169 | /** 170 | * @inheritdoc 171 | */ 172 | public function getAttributeSetName(): string 173 | { 174 | return 'Default'; 175 | } 176 | 177 | /** 178 | * @inheritdoc 179 | */ 180 | public function getIdentities(): array 181 | { 182 | $identities = [self::CACHE_TAG . '_' . $this->getId()]; 183 | if ($this->_appState->getAreaCode() == Area::AREA_FRONTEND) { 184 | $identities[] = self::CACHE_TAG; 185 | } 186 | 187 | return array_unique($identities); 188 | } 189 | 190 | /** 191 | * @inheritdoc 192 | */ 193 | protected function getCustomAttributesCodes(): array 194 | { 195 | if ($this->customAttributesCodes === null) { 196 | $this->customAttributesCodes = $this->getEavAttributesCodes($this->metadataService); 197 | $this->customAttributesCodes = array_diff($this->customAttributesCodes, $this->interfaceAttributes); 198 | } 199 | 200 | return $this->customAttributesCodes; 201 | } 202 | 203 | /** 204 | * @inheritdoc 205 | */ 206 | public function getMediaPath(): ?string 207 | { 208 | return $this->getData(self::MEDIA_PATH); 209 | } 210 | 211 | /** 212 | * @inheritdoc 213 | */ 214 | public function setMediaPath(string $path): self 215 | { 216 | return $this->setData(self::MEDIA_PATH, $path); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /Model/Seller/Attribute/Repository.php: -------------------------------------------------------------------------------- 1 | getList($this->searchCriteriaBuilder->create())->getItems(); 33 | } 34 | 35 | /** 36 | * @inheritdoc 37 | */ 38 | public function getList(SearchCriteriaInterface $searchCriteria): AttributeSearchResultsInterface 39 | { 40 | return $this->eavAttributeRepository->getList( 41 | SellerInterface::ENTITY, 42 | $searchCriteria 43 | ); 44 | } 45 | 46 | /** 47 | * @inheritdoc 48 | */ 49 | public function get(string $attributeCode): AttributeInterface|SellerAttributeInterface 50 | { 51 | return $this->eavAttributeRepository->get( 52 | SellerInterface::ENTITY, 53 | $attributeCode 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Model/Seller/Attribute/ScopeOverriddenValue.php: -------------------------------------------------------------------------------- 1 | resourceConnection = $resourceConnection->getConnection(); 38 | } 39 | 40 | /** 41 | * Whether attribute value is overridden in specific store. 42 | * 43 | * @throws LocalizedException 44 | */ 45 | public function containsValue(SellerInterface $entity, string $attributeCode, int $storeId): bool 46 | { 47 | if ((int) $storeId === Store::DEFAULT_STORE_ID) { 48 | return false; 49 | } 50 | if ($this->attributesValues === null) { 51 | $this->initAttributeValues($entity, (int) $storeId); 52 | } 53 | 54 | return isset($this->attributesValues[$storeId]) 55 | && array_key_exists($attributeCode, $this->attributesValues[$storeId]); 56 | } 57 | 58 | /** 59 | * Init Attributes Values. 60 | * 61 | * @throws LocalizedException 62 | */ 63 | private function initAttributeValues(SellerInterface $entity, int $storeId): void 64 | { 65 | $attributeTables = []; 66 | 67 | /** @var AbstractAttribute $attribute */ 68 | foreach ($this->getScopedAttributes() as $attribute) { 69 | if (!$attribute->isStatic()) { 70 | $attributeTables[$attribute->getBackend()->getTable()][] = $attribute->getAttributeId(); 71 | } 72 | } 73 | 74 | $storeIds = [Store::DEFAULT_STORE_ID]; 75 | if ($storeId !== Store::DEFAULT_STORE_ID) { 76 | $storeIds[] = $storeId; 77 | } 78 | 79 | $selects = []; 80 | foreach ($attributeTables as $attributeTable => $attributeCodes) { 81 | $select = $this->resourceConnection->select() 82 | ->from(['t' => $attributeTable], ['value' => 't.value', 'store_id' => 't.store_id']) 83 | ->join( 84 | ['a' => $this->resourceConnection->getTableName('eav_attribute')], 85 | 'a.attribute_id = t.attribute_id', 86 | ['attribute_code' => 'a.attribute_code'] 87 | ) 88 | ->where('entity_id = ?', $entity->getId()) 89 | ->where('t.attribute_id IN (?)', $attributeCodes) 90 | ->where('t.store_id IN (?)', $storeIds); 91 | $selects[] = $select; 92 | } 93 | 94 | $unionSelect = new UnionExpression($selects, Select::SQL_UNION_ALL); 95 | 96 | $attributes = $this->resourceConnection->fetchAll((string) $unionSelect); 97 | foreach ($attributes as $attribute) { 98 | $this->attributesValues[$attribute['store_id']][$attribute['attribute_code']] = $attribute['value']; 99 | } 100 | } 101 | 102 | /** 103 | * Retrieve a list of attributes that can be scoped by store. 104 | * 105 | * @return AttributeInterface[] 106 | */ 107 | private function getScopedAttributes(): array 108 | { 109 | $searchResult = $this->attributeRepository->getList( 110 | $this->searchCriteriaBuilder->addFilters([])->create() 111 | ); 112 | 113 | return array_filter($searchResult->getItems(), function ($item) { 114 | /** @var SellerAttributeInterface $item */ 115 | return !$item->isScopeGlobal(); 116 | }); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Model/Seller/DataProvider.php: -------------------------------------------------------------------------------- 1 | mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); 33 | } 34 | 35 | /** 36 | * Remove media From Tmp Directory. 37 | * 38 | * @throws LocalizedException 39 | */ 40 | public function removeMediaFromTmp(SellerInterface $seller): void 41 | { 42 | $media = str_replace($seller->getData('retailer_id') . '_', '', $seller->getMediaPath()); 43 | $baseTmpPath = $this->imageUploader->getBaseTmpPath(); 44 | if (!empty($media) && $this->pathExist($baseTmpPath, $media)) { 45 | $this->moveFileFromTmp($media, $seller); 46 | } 47 | } 48 | 49 | /** 50 | * Move media From Tmp Directory. 51 | * 52 | * @throws LocalizedException 53 | */ 54 | public function moveFileFromTmp(string $imageName, SellerInterface $seller): void 55 | { 56 | $baseTmpPath = $this->imageUploader->getBaseTmpPath(); 57 | $basePath = $this->imageUploader->getBasePath(); 58 | 59 | $baseImagePath = $this->imageUploader->getFilePath( 60 | $basePath, 61 | $seller->getData('retailer_id') . '_' . $imageName 62 | ); 63 | $baseTmpImagePath = $this->imageUploader->getFilePath($baseTmpPath, $imageName); 64 | 65 | try { 66 | $this->coreFileStorageDatabase->copyFile( 67 | $baseTmpImagePath, 68 | $baseImagePath 69 | ); 70 | $this->mediaDirectory->renameFile( 71 | $baseTmpImagePath, 72 | $baseImagePath 73 | ); 74 | } catch (Exception $e) { 75 | throw new LocalizedException( 76 | __('Something went wrong while saving the file(s).') 77 | ); 78 | } 79 | } 80 | 81 | /** 82 | * Remove File. 83 | * 84 | * @throws FileSystemException 85 | */ 86 | public function removeMedia(SellerInterface $seller): void 87 | { 88 | $media = $seller->getData('retailer_id') . '_' . $seller->getMediaPath(); 89 | $basePath = $this->imageUploader->getBasePath(); 90 | if ($seller->getData('retailer_id') && $seller->getMediaPath() && $this->pathExist($basePath, $media)) { 91 | $this->removeFile($media); 92 | } 93 | } 94 | 95 | /** 96 | * Check if media exist. 97 | * 98 | * @throws FileSystemException 99 | */ 100 | public function pathExist(string $basePath, string $fileName): bool 101 | { 102 | return file_exists($this->getPath($basePath, $fileName)); 103 | } 104 | 105 | /** 106 | * Get media Path. 107 | * 108 | * @throws FileSystemException 109 | */ 110 | public function getPath(string $basePath, string $fileName): string 111 | { 112 | return $this->directoryList->getPath(DirectoryList::MEDIA) . 113 | '/' 114 | . $this->imageUploader->getFilePath($basePath, $fileName); 115 | } 116 | 117 | /** 118 | * Remove File. 119 | * 120 | * @throws FileSystemException 121 | */ 122 | public function removeFile(string $fileName): void 123 | { 124 | unlink($this->getPath($this->imageUploader->getBasePath(), $fileName)); 125 | } 126 | 127 | /** 128 | * Retrieve MIME type of requested file. 129 | */ 130 | public function getMimeType(string $fileName): string 131 | { 132 | $filePath = $this->getPath($this->imageUploader->getBasePath(), $fileName); 133 | $absoluteFilePath = $this->mediaDirectory->getAbsolutePath($filePath); 134 | 135 | $result = $this->mime->getMimeType($absoluteFilePath); 136 | 137 | return $result; 138 | } 139 | 140 | /** 141 | * Get file statistics data. 142 | */ 143 | public function getStat(string $fileName): array 144 | { 145 | $filePath = $this->getPath($this->imageUploader->getBasePath(), $fileName); 146 | 147 | return $this->mediaDirectory->stat($filePath); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Model/SellerRepository.php: -------------------------------------------------------------------------------- 1 | applyAttributeSet($seller); 42 | 43 | $this->resourceModel->beforeSave($seller); 44 | $seller = $this->entityManager->save($seller); 45 | $this->resourceModel->afterSave($seller); 46 | 47 | unset($this->sellerRepositoryById[$seller->getId()]); 48 | 49 | return $seller; 50 | } 51 | 52 | /** 53 | * Get info about seller by seller id. 54 | * 55 | * @throws NoSuchEntityException 56 | */ 57 | public function get(int $sellerId, ?int $storeId = null): SellerInterface 58 | { 59 | $cacheKey = $storeId ?? 'all'; 60 | 61 | if (!isset($this->sellerRepositoryById[$sellerId][$cacheKey])) { 62 | /** @var SellerModel $sellerModel */ 63 | $sellerModel = $this->sellerFactory->create(); 64 | 65 | if (null !== $storeId) { 66 | $sellerModel->setData('store_id', $storeId); 67 | } 68 | 69 | $seller = $this->entityManager->load($sellerModel, (string) $sellerId); 70 | $this->resourceModel->afterLoad($sellerModel); 71 | 72 | if (!$seller->getId()) { 73 | $exception = new NoSuchEntityException(); 74 | throw $exception->singleField($seller->getIdFieldName(), $sellerId); 75 | } 76 | 77 | $this->sellerRepositoryById[$sellerId][$cacheKey] = $seller; 78 | } 79 | 80 | return $this->sellerRepositoryById[$sellerId][$cacheKey]; 81 | } 82 | 83 | /** 84 | * Retrieve seller by seller code. 85 | * 86 | * @throws NoSuchEntityException 87 | */ 88 | public function getByCode(string $sellerCode, ?int $storeId = null): SellerInterface 89 | { 90 | $sellerId = $this->resourceModel->getIdByCode($sellerCode); 91 | if (!$sellerId) { 92 | throw new NoSuchEntityException(__('Requested seller doesn\'t exist')); 93 | } 94 | 95 | return $this->get($sellerId, $storeId); 96 | } 97 | 98 | /** 99 | * Delete seller. 100 | * 101 | * @throws NoSuchEntityException 102 | * @throws InputException 103 | * @throws StateException 104 | */ 105 | public function delete(SellerInterface $seller): bool 106 | { 107 | /** @var SellerModel $seller */ 108 | $sellerId = $seller->getId(); 109 | 110 | $this->resourceModel->beforeDelete($seller); 111 | $deleteResult = $this->entityManager->delete($seller); 112 | $this->resourceModel->afterDelete($seller); 113 | 114 | if ($deleteResult && isset($this->sellerRepositoryById[$sellerId])) { 115 | unset($this->sellerRepositoryById[$sellerId]); 116 | } 117 | 118 | return $deleteResult; 119 | } 120 | 121 | /** 122 | * Delete seller by identifier. 123 | * 124 | * @throws NoSuchEntityException 125 | * @throws InputException 126 | * @throws StateException 127 | */ 128 | public function deleteByIdentifier(int $sellerId): bool 129 | { 130 | $seller = $this->get($sellerId); 131 | 132 | return $this->delete($seller); 133 | } 134 | 135 | /** 136 | * Retrieve Attribute Set Id to use for this entity, if any. 137 | */ 138 | public function getEntityAttributeSetId(): ?int 139 | { 140 | $attributeSetId = null; 141 | 142 | if (null !== $this->sellerAttributeSetName) { 143 | /** @var SellerModel $sellerModel */ 144 | $sellerModel = $this->sellerFactory->create(); 145 | /** @var ResourceModel $resourceModel */ 146 | $resourceModel = $sellerModel->getResource(); 147 | $attributeSetId = $resourceModel->getAttributeSetIdByName($this->sellerAttributeSetName); 148 | } 149 | 150 | return $attributeSetId; 151 | } 152 | 153 | /** 154 | * Apply correct attribute set to the current seller item. 155 | */ 156 | private function applyAttributeSet(SellerInterface $seller): SellerInterface 157 | { 158 | // add a fallback in case retailer attribute_set_id is not correctly returned from Retailer entity 159 | if (null === $this->sellerAttributeSetName && $seller->getAttributeSetName()) { 160 | $this->sellerAttributeSetName = $seller->getAttributeSetName(); 161 | } 162 | 163 | $attributeSetId = $this->getEntityAttributeSetId(); 164 | if (null !== $attributeSetId) { 165 | $seller->setData('attribute_set_id', $attributeSetId); 166 | } 167 | 168 | return $seller; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Setup/Patch/Data/DefaultSellerAttributes.php: -------------------------------------------------------------------------------- 1 | sellerSetupFactory->create(['setup' => $this->moduleDataSetup]); 32 | $sellerSetup->installEntities(); 33 | 34 | return $this; 35 | } 36 | 37 | /** 38 | * @inheritdoc 39 | */ 40 | public static function getDependencies(): array 41 | { 42 | return []; 43 | } 44 | 45 | /** 46 | * @inheritdoc 47 | */ 48 | public static function getVersion(): string 49 | { 50 | return '2.0.1'; 51 | } 52 | 53 | /** 54 | * @inheritdoc 55 | */ 56 | public function getAliases(): array 57 | { 58 | return []; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Setup/Patch/SellerSetup.php: -------------------------------------------------------------------------------- 1 | [ 26 | 'entity_model' => Seller::class, 27 | 'attribute_model' => Attribute::class, 28 | 'table' => 'smile_seller_entity', 29 | 'entity_attribute_collection' => AttributeCollection::class, 30 | 'attributes' => [ 31 | 'name' => [ 32 | 'type' => 'varchar', 33 | 'label' => 'Name', 34 | 'input' => 'text', 35 | 'sort_order' => 1, 36 | ], 37 | 'created_at' => [ 38 | 'type' => 'static', 39 | 'input' => 'date', 40 | 'sort_order' => 19, 41 | 'visible' => false, 42 | ], 43 | 'updated_at' => [ 44 | 'type' => 'static', 45 | 'input' => 'date', 46 | 'sort_order' => 20, 47 | ], 48 | 'seller_code' => [ 49 | 'type' => 'static', 50 | 'label' => 'Seller Code', 51 | 'input' => 'text', 52 | 'frontend_class' => 'validate-length maximum-length-64', 53 | 'unique' => true, 54 | ], 55 | 'is_active' => [ 56 | 'type' => 'int', 57 | 'label' => 'Is Active', 58 | 'input' => 'select', 59 | 'source' => Boolean::class, 60 | 'sort_order' => 2, 61 | ], 62 | 'description' => [ 63 | 'type' => 'text', 64 | 'label' => 'Description', 65 | 'input' => 'textarea', 66 | 'required' => false, 67 | 'sort_order' => 4, 68 | ], 69 | 'meta_title' => [ 70 | 'type' => 'varchar', 71 | 'label' => 'Page Title', 72 | 'input' => 'text', 73 | 'required' => false, 74 | 'sort_order' => 6, 75 | ], 76 | 'meta_keywords' => [ 77 | 'type' => 'text', 78 | 'label' => 'Meta Keywords', 79 | 'input' => 'textarea', 80 | 'required' => false, 81 | 'sort_order' => 7, 82 | ], 83 | 'meta_description' => [ 84 | 'type' => 'text', 85 | 'label' => 'Meta Description', 86 | 'input' => 'textarea', 87 | 'required' => false, 88 | 'sort_order' => 8, 89 | ], 90 | ], 91 | ], 92 | ]; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Ui/Component/Seller/Form/DataProvider.php: -------------------------------------------------------------------------------- 1 | meta = $this->prepareMeta($meta); 28 | } 29 | 30 | /** 31 | * @inheritdoc 32 | */ 33 | public function getData() 34 | { 35 | $data = parent::getData(); 36 | 37 | foreach ($this->pool->getModifiersInstances() as $modifier) { 38 | $data = $modifier->modifyData($data); 39 | } 40 | 41 | return $data; 42 | } 43 | 44 | /** 45 | * @inheritdoc 46 | */ 47 | public function getCollection() 48 | { 49 | if ($this->collection === null) { 50 | $this->collection = $this->collectionFactory->create(); 51 | $this->collection->addAttributeToSelect('*'); 52 | if ($this->locator->getStore()) { 53 | $this->collection->setStoreId($this->locator->getStore()->getId()); 54 | } 55 | } 56 | 57 | return $this->collection; 58 | } 59 | 60 | /** 61 | * Prepare meta data. 62 | */ 63 | private function prepareMeta(array $meta): array 64 | { 65 | foreach ($this->pool->getModifiersInstances() as $modifier) { 66 | $meta = $modifier->modifyMeta($meta); 67 | } 68 | 69 | return $meta; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Ui/Component/Seller/Form/FieldMapper.php: -------------------------------------------------------------------------------- 1 | initFieldsMap($attributeCollectionFactory, $attributeSetId); 28 | } 29 | 30 | /** 31 | * Attribute collection for the current mapper. 32 | */ 33 | public function getAttributesCollection(): Collection 34 | { 35 | return $this->attributesCollection; 36 | } 37 | 38 | /** 39 | * Mapping of the attribute by fieldsets. 40 | */ 41 | public function getFieldsMap(): array 42 | { 43 | return $this->fieldsMap; 44 | } 45 | 46 | /** 47 | * Fieldset properties. 48 | */ 49 | public function getFieldsets(): array 50 | { 51 | return $this->fieldsets; 52 | } 53 | 54 | /** 55 | * Init attribute collection, fieldsets and mapping. 56 | */ 57 | private function initFieldsMap( 58 | AttributeCollectionFactory $attributeCollectionFactory, 59 | string $attributeSetId 60 | ): FieldMapper { 61 | 62 | $this->fieldsMap = []; 63 | $this->attributesCollection = $attributeCollectionFactory->create(); 64 | $this->attributesCollection->setAttributeSetFilterBySetName($attributeSetId, SellerInterface::ENTITY); 65 | $this->attributesCollection->addSetInfo(); 66 | 67 | foreach ($this->attributesCollection as $attribute) { 68 | $attributeGroupId = $attribute->getAttributeGroupId(); 69 | /** @var Group $attributeGroup */ 70 | $attributeGroup = $this->attributeGroupRepository->get($attributeGroupId); 71 | $fieldsetCode = str_replace('-', '_', $attributeGroup->getAttributeGroupCode()); 72 | $this->fieldsets[$fieldsetCode] = [ 73 | 'name' => $attributeGroup->getAttributeGroupName(), 74 | 'sortOrder' => $attributeGroup->getSortOrder(), 75 | ]; 76 | $this->fieldsMap[$fieldsetCode][] = $attribute->getAttributeCode(); 77 | } 78 | 79 | return $this; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Ui/Component/Seller/Form/Modifier/Eav.php: -------------------------------------------------------------------------------- 1 | 'frontend_input', 46 | 'required' => 'is_required', 47 | 'label' => 'frontend_label', 48 | 'sortOrder' => 'sort_order', 49 | 'notice' => 'note', 50 | 'default' => 'default_value', 51 | 'size' => 'multiline_count', 52 | ]; 53 | 54 | /** 55 | * Form element mapping. 56 | */ 57 | private array $formElement = [ 58 | 'text' => 'input', 59 | 'boolean' => 'checkbox', 60 | ]; 61 | 62 | private array $validationRules = [ 63 | 'email' => ['validate-email' => true], 64 | 'date' => ['validate-date' => true], 65 | ]; 66 | 67 | public function __construct( 68 | private LocatorInterface $locator, 69 | private ScopeOverriddenValue $scopeOverriddenValue, 70 | StoreManagerInterface $storeManagerInterface, 71 | AttributeRepositoryInterface $attributeRepositoryInterface, 72 | private EavValidationRules $eavValidationRules, 73 | private FieldMapper $fieldMapper, 74 | private SellerMediaUpload $media 75 | ) { 76 | $this->storeManager = $storeManagerInterface; 77 | $this->attributeRepository = $attributeRepositoryInterface; 78 | } 79 | 80 | /** 81 | * @inheritdoc 82 | */ 83 | public function modifyData(array $data) 84 | { 85 | if ($this->locator->getSeller()) { 86 | if (isset($data[$this->locator->getSeller()->getId()])) { 87 | $data[$this->locator->getSeller()->getId()]['store_id'] = $this->locator->getStore()->getId(); 88 | $data[$this->locator->getSeller()->getId()] = $this->convertValues( 89 | $this->locator->getSeller(), 90 | $data[$this->locator->getSeller()->getId()] 91 | ); 92 | } 93 | } 94 | 95 | return $data; 96 | } 97 | 98 | /** 99 | * @inheritdoc 100 | */ 101 | public function modifyMeta(array $meta) 102 | { 103 | return array_replace_recursive( 104 | $meta, 105 | $this->prepareFieldsMeta($this->getFieldsMap(), $this->getAttributesMeta()) 106 | ); 107 | } 108 | 109 | /** 110 | * Get attributes meta. 111 | * 112 | * @throws LocalizedException 113 | */ 114 | private function getAttributesMeta(): array 115 | { 116 | $meta = []; 117 | 118 | /** @var SellerAttributeInterface|AbstractAttribute $attribute */ 119 | foreach ($this->getAttributes()->getItems() as $attribute) { 120 | $code = $attribute->getAttributeCode(); 121 | 122 | foreach ($this->metaProperties as $metaName => $origName) { 123 | $value = $attribute->getDataUsingMethod($origName); 124 | 125 | $meta[$code][$metaName] = $value; 126 | if ('frontend_input' === $origName) { 127 | $meta[$code]['formElement'] = $this->formElement[$value] ?? $value; 128 | } 129 | if ($attribute->usesSource()) { 130 | $meta[$code]['options'] = $attribute->getSource()->getAllOptions(); 131 | } 132 | } 133 | 134 | $rules = $this->eavValidationRules->build($attribute, $meta[$code]); 135 | if ($attribute->getFrontendInput() && isset($this->validationRules[$attribute->getFrontendInput()])) { 136 | // phpcs:ignore Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge 137 | $rules = array_merge($rules, $this->validationRules[$attribute->getFrontendInput()]); 138 | } 139 | 140 | if (!empty($rules)) { 141 | $meta[$code]['validation'] = $rules; 142 | } 143 | 144 | $meta[$code]['label'] = __($meta[$code]['label']); 145 | $meta[$code] += $this->customizeCheckbox($attribute); 146 | $meta[$code]['componentType'] = Field::NAME; 147 | $meta[$code] += $this->addUseDefaultValueCheckbox($attribute); 148 | $meta[$code]['scopeLabel'] = $this->getScopeLabel($attribute); 149 | } 150 | 151 | return $meta; 152 | } 153 | 154 | /** 155 | * List of EAV attributes of the current model. 156 | */ 157 | private function getAttributes(): Collection 158 | { 159 | return $this->fieldMapper->getAttributesCollection(); 160 | } 161 | 162 | /** 163 | * Field map by fieldset code. 164 | */ 165 | private function getFieldsMap(): array 166 | { 167 | return $this->fieldMapper->getFieldsMap(); 168 | } 169 | 170 | /** 171 | * Prepare fields meta based on xml declaration of form and fields metadata. 172 | */ 173 | private function prepareFieldsMeta(array $fieldsMap, array $fieldsMeta): array 174 | { 175 | $result = []; 176 | $fieldsets = $this->fieldMapper->getFieldsets(); 177 | 178 | foreach ($fieldsMap as $fieldSet => $fields) { 179 | foreach ($fields as $field) { 180 | if (!isset($result[$fieldSet])) { 181 | $result[$fieldSet]['arguments']['data']['config'] = [ 182 | 'componentType' => Fieldset::NAME, 183 | 'label' => __($fieldsets[$fieldSet]['name']), 184 | 'sortOrder' => $fieldsets[$fieldSet]['sortOrder'], 185 | 'collapsible' => true, 186 | ]; 187 | } 188 | 189 | if (isset($fieldsMeta[$field])) { 190 | $result[$fieldSet]['children'][$field]['arguments']['data']['config'] = $fieldsMeta[$field]; 191 | } 192 | } 193 | } 194 | 195 | return $result; 196 | } 197 | 198 | /** 199 | * Retrieve label of attribute scope (global, website, store). 200 | */ 201 | private function getScopeLabel(mixed $attribute): string 202 | { 203 | $html = ''; 204 | if ( 205 | !$attribute || $this->storeManager->isSingleStoreMode() 206 | || $attribute->getFrontendInput() === AttributeInterface::FRONTEND_INPUT 207 | ) { 208 | return $html; 209 | } 210 | 211 | if ($attribute->isScopeGlobal()) { 212 | $html .= __('[GLOBAL]'); 213 | } elseif ($attribute->isScopeWebsite()) { 214 | $html .= __('[WEBSITE]'); 215 | } elseif ($attribute->isScopeStore()) { 216 | $html .= __('[STORE VIEW]'); 217 | } 218 | 219 | return $html; 220 | } 221 | 222 | /** 223 | * Add the "Use Default Value" checkbox if needed. 224 | */ 225 | private function addUseDefaultValueCheckbox(SellerAttributeInterface $attribute): array 226 | { 227 | $canDisplayService = $this->canDisplayUseDefault($attribute); 228 | $meta = []; 229 | 230 | if ($canDisplayService) { 231 | $meta['service'] = ['template' => 'ui/form/element/helper/service']; 232 | $meta['disabled'] = !$this->scopeOverriddenValue->containsValue( 233 | $this->locator->getSeller(), 234 | $attribute->getAttributeCode(), 235 | (int) $this->locator->getStore()->getId() 236 | ); 237 | } 238 | 239 | return $meta; 240 | } 241 | 242 | /** 243 | * Whether attribute can have default value. 244 | */ 245 | private function canDisplayUseDefault(SellerAttributeInterface $attribute): bool 246 | { 247 | $attributeCode = $attribute->getAttributeCode(); 248 | 249 | /** @var ResourceModelSeller|SellerInterface|null $seller */ 250 | $seller = $this->locator->getSeller(); 251 | 252 | if (isset($this->canDisplayUseDefault[$attributeCode])) { 253 | return $this->canDisplayUseDefault[$attributeCode]; 254 | } 255 | 256 | return $this->canDisplayUseDefault[$attributeCode] = ( 257 | !$attribute->isScopeGlobal() 258 | && $seller 259 | && $seller->getId() 260 | && $seller->getStoreId() 261 | ); 262 | } 263 | 264 | /** 265 | * Customize checkboxes. 266 | */ 267 | private function customizeCheckbox(SellerAttributeInterface $attribute): array 268 | { 269 | $meta = []; 270 | 271 | if ($attribute->getFrontendInput() === 'boolean') { 272 | $meta['prefer'] = 'toggle'; 273 | $meta['valueMap'] = [ 274 | 'true' => '1', 275 | 'false' => '0', 276 | ]; 277 | } 278 | 279 | return $meta; 280 | } 281 | 282 | /** 283 | * Converts category image data to acceptable for rendering format. 284 | */ 285 | private function convertValues(SellerInterface $seller, array $data): array 286 | { 287 | foreach ($this->getAttributes() as $attribute) { 288 | $attributeCode = $attribute->getAttributeCode(); 289 | if (!isset($data[$attributeCode])) { 290 | continue; 291 | } 292 | 293 | if ($attribute->getBackend() instanceof ImageBackendModel || $attribute->getFrontendInput() === 'image') { 294 | unset($data[$attributeCode]); 295 | $fileName = $seller->getData($attributeCode); 296 | $stat = $this->media->getStat($fileName); 297 | $mime = $this->media->getMimeType($fileName); 298 | 299 | $data[$attributeCode][0]['name'] = $fileName; 300 | $data[$attributeCode][0]['url'] = $this->getBaseImageUrl() . $fileName; 301 | $data[$attributeCode][0]['size'] = isset($stat['size']) ?: 0; 302 | $data[$attributeCode][0]['type'] = $mime; 303 | } 304 | } 305 | 306 | return $data; 307 | } 308 | 309 | /** 310 | * Get base image url. 311 | */ 312 | public function getBaseImageUrl(): string 313 | { 314 | /** @var Store $currentStore */ 315 | $currentStore = $this->storeManager->getStore(); 316 | $mediaUrl = $currentStore->getBaseUrl(UrlInterface::URL_TYPE_MEDIA); 317 | 318 | return $mediaUrl . 'seller/'; 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /Ui/Component/Seller/Listing/AddStoreFieldToCollection.php: -------------------------------------------------------------------------------- 1 | storeManager->getStore($condition['eq']); 31 | /** @var \Smile\Seller\Model\ResourceModel\Seller\Collection $collection */ 32 | // @phpstan-ignore-next-line as generated object 33 | $collection->setStore($store); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Ui/Component/Seller/Listing/DataProvider.php: -------------------------------------------------------------------------------- 1 | collection = $collectionFactory->create(); 33 | } 34 | 35 | /** 36 | * Get data. 37 | */ 38 | public function getData(): array 39 | { 40 | if (!$this->getCollection()->isLoaded()) { 41 | $this->getCollection()->load(); 42 | } 43 | $items = $this->getCollection()->toArray(); 44 | 45 | return [ 46 | 'totalRecords' => $this->getCollection()->getSize(), 47 | 'items' => array_values($items), 48 | ]; 49 | } 50 | 51 | /** 52 | * @inheritdoc 53 | */ 54 | public function addField($field, $alias = null) 55 | { 56 | if (isset($this->addFieldStrategies[$field])) { 57 | $this->addFieldStrategies[$field]->addField($this->getCollection(), $field, $alias); 58 | 59 | return ; 60 | } 61 | parent::addField($field, $alias); 62 | } 63 | 64 | /** 65 | * @inheritdoc 66 | */ 67 | public function addFilter(Filter $filter): void 68 | { 69 | if (isset($this->addFilterStrategies[$filter->getField()])) { 70 | $this->addFilterStrategies[$filter->getField()] 71 | ->addFilter( 72 | $this->getCollection(), 73 | $filter->getField(), 74 | [$filter->getConditionType() => $filter->getValue()] 75 | ); 76 | } 77 | if (!isset($this->addFilterStrategies[$filter->getField()])) { 78 | parent::addFilter($filter); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smile/module-seller", 3 | "type": "magento2-component", 4 | "description": "Smile Retailer Suite - Seller Module", 5 | "keywords": ["magento2", "retailer suite", "retailer", "shop", "seller"], 6 | "license": "OSL-3.0", 7 | "authors": [ 8 | { 9 | "name": "Aurélien Foucret", 10 | "email": "aurelien.foucret@smile.fr" 11 | }, 12 | { 13 | "name": "Romain Ruaud", 14 | "email": "romain.ruaud@smile.fr" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.1", 19 | "magento/framework": ">=103.0.4", 20 | "magento/module-eav": ">=102.1.4", 21 | "magento/module-store": ">=101.1.4" 22 | }, 23 | "require-dev": { 24 | "smile/magento2-smilelab-quality-suite": "^3.0" 25 | }, 26 | "repositories": [ 27 | { 28 | "type": "composer", 29 | "url": "https://repo.magento.com/" 30 | } 31 | ], 32 | "minimum-stability": "dev", 33 | "prefer-stable": true, 34 | "autoload": { 35 | "files": [ 36 | "registration.php" 37 | ], 38 | "psr-4": { 39 | "Smile\\Seller\\": "" 40 | } 41 | }, 42 | "config": { 43 | "allow-plugins": { 44 | "magento/composer-dependency-version-audit-plugin": true, 45 | "dealerdirect/phpcodesniffer-composer-installer": true 46 | }, 47 | "sort-packages": true 48 | }, 49 | "extra": { 50 | "map": [ 51 | [ 52 | "lib/web/fonts/retailersuite", 53 | "lib/web/fonts/retailersuite" 54 | ] 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /etc/adminhtml/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Smile\Seller\Ui\Component\Seller\Listing\AddStoreFieldToCollection 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /etc/adminhtml/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /etc/db_schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 29 | 31 | 33 | 35 | 37 | 39 | 40 | 41 | 42 | 43 | 47 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 | 74 | 76 | 78 | 80 | 82 | 84 | 85 | 86 | 87 | 88 | 92 | 96 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
117 | 119 | 121 | 123 | 125 | 127 | 129 | 130 | 131 | 132 | 133 | 137 | 141 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 |
162 | 164 | 166 | 168 | 170 | 172 | 174 | 175 | 176 | 177 | 178 | 182 | 186 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 |
207 | 209 | 211 | 213 | 215 | 217 | 219 | 220 | 221 | 222 | 223 | 227 | 231 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 |
252 | 253 | 254 | 255 | 257 | 259 | 263 |
264 | 265 | 267 | 269 | 273 |
274 |
275 | -------------------------------------------------------------------------------- /etc/db_schema_whitelist.json: -------------------------------------------------------------------------------- 1 | { 2 | "smile_seller_entity": { 3 | "column": { 4 | "entity_id": true, 5 | "attribute_set_id": true, 6 | "seller_code": true, 7 | "created_at": true, 8 | "updated_at": true 9 | }, 10 | "index": { 11 | "SMILE_SELLER_ENTITY_SELLER_CODE": true 12 | }, 13 | "constraint": { 14 | "PRIMARY": true 15 | } 16 | }, 17 | "smile_seller_entity_datetime": { 18 | "column": { 19 | "value_id": true, 20 | "attribute_id": true, 21 | "store_id": true, 22 | "entity_id": true, 23 | "value": true 24 | }, 25 | "index": { 26 | "SMILE_SELLER_ENTITY_DATETIME_ENTITY_ID": true, 27 | "SMILE_SELLER_ENTITY_DATETIME_ATTRIBUTE_ID": true, 28 | "SMILE_SELLER_ENTITY_DATETIME_STORE_ID": true 29 | }, 30 | "constraint": { 31 | "PRIMARY": true, 32 | "SMILE_SELLER_ENTT_DTIME_ATTR_ID_EAV_ATTR_ATTR_ID": true, 33 | "SMILE_SELLER_ENTT_DTIME_ENTT_ID_SMILE_SELLER_ENTT_ENTT_ID": true, 34 | "SMILE_SELLER_ENTITY_DATETIME_STORE_ID_STORE_STORE_ID": true, 35 | "SMILE_SELLER_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true 36 | } 37 | }, 38 | "smile_seller_entity_decimal": { 39 | "column": { 40 | "value_id": true, 41 | "attribute_id": true, 42 | "store_id": true, 43 | "entity_id": true, 44 | "value": true 45 | }, 46 | "index": { 47 | "SMILE_SELLER_ENTITY_DECIMAL_ENTITY_ID": true, 48 | "SMILE_SELLER_ENTITY_DECIMAL_ATTRIBUTE_ID": true, 49 | "SMILE_SELLER_ENTITY_DECIMAL_STORE_ID": true 50 | }, 51 | "constraint": { 52 | "PRIMARY": true, 53 | "SMILE_SELLER_ENTT_DEC_ATTR_ID_EAV_ATTR_ATTR_ID": true, 54 | "SMILE_SELLER_ENTT_DEC_ENTT_ID_SMILE_SELLER_ENTT_ENTT_ID": true, 55 | "SMILE_SELLER_ENTITY_DECIMAL_STORE_ID_STORE_STORE_ID": true, 56 | "SMILE_SELLER_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true 57 | } 58 | }, 59 | "smile_seller_entity_int": { 60 | "column": { 61 | "value_id": true, 62 | "attribute_id": true, 63 | "store_id": true, 64 | "entity_id": true, 65 | "value": true 66 | }, 67 | "index": { 68 | "SMILE_SELLER_ENTITY_INT_ENTITY_ID": true, 69 | "SMILE_SELLER_ENTITY_INT_ATTRIBUTE_ID": true, 70 | "SMILE_SELLER_ENTITY_INT_STORE_ID": true 71 | }, 72 | "constraint": { 73 | "PRIMARY": true, 74 | "SMILE_SELLER_ENTITY_INT_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, 75 | "SMILE_SELLER_ENTITY_INT_ENTITY_ID_SMILE_SELLER_ENTITY_ENTITY_ID": true, 76 | "SMILE_SELLER_ENTITY_INT_STORE_ID_STORE_STORE_ID": true, 77 | "SMILE_SELLER_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true 78 | } 79 | }, 80 | "smile_seller_entity_text": { 81 | "column": { 82 | "value_id": true, 83 | "attribute_id": true, 84 | "store_id": true, 85 | "entity_id": true, 86 | "value": true 87 | }, 88 | "index": { 89 | "SMILE_SELLER_ENTITY_TEXT_ENTITY_ID": true, 90 | "SMILE_SELLER_ENTITY_TEXT_ATTRIBUTE_ID": true, 91 | "SMILE_SELLER_ENTITY_TEXT_STORE_ID": true 92 | }, 93 | "constraint": { 94 | "PRIMARY": true, 95 | "SMILE_SELLER_ENTITY_TEXT_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, 96 | "SMILE_SELLER_ENTITY_TEXT_ENTITY_ID_SMILE_SELLER_ENTITY_ENTITY_ID": true, 97 | "SMILE_SELLER_ENTITY_TEXT_STORE_ID_STORE_STORE_ID": true, 98 | "SMILE_SELLER_ENTITY_TEXT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true 99 | } 100 | }, 101 | "smile_seller_entity_varchar": { 102 | "column": { 103 | "value_id": true, 104 | "attribute_id": true, 105 | "store_id": true, 106 | "entity_id": true, 107 | "value": true 108 | }, 109 | "index": { 110 | "SMILE_SELLER_ENTITY_VARCHAR_ENTITY_ID": true, 111 | "SMILE_SELLER_ENTITY_VARCHAR_ATTRIBUTE_ID": true, 112 | "SMILE_SELLER_ENTITY_VARCHAR_STORE_ID": true 113 | }, 114 | "constraint": { 115 | "PRIMARY": true, 116 | "SMILE_SELLER_ENTT_VCHR_ATTR_ID_EAV_ATTR_ATTR_ID": true, 117 | "SMILE_SELLER_ENTT_VCHR_ENTT_ID_SMILE_SELLER_ENTT_ENTT_ID": true, 118 | "SMILE_SELLER_ENTITY_VARCHAR_STORE_ID_STORE_STORE_ID": true, 119 | "SMILE_SELLER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true 120 | } 121 | }, 122 | "sales_order": { 123 | "column": { 124 | "seller_id": true, 125 | "pickup_date": true 126 | }, 127 | "constraint": { 128 | "SALES_ORDER_SELLER_ID_SMILE_SELLER_ENTITY_ENTITY_ID": true 129 | } 130 | }, 131 | "quote": { 132 | "column": { 133 | "seller_id": true, 134 | "pickup_date": true 135 | }, 136 | "constraint": { 137 | "QUOTE_SELLER_ID_SMILE_SELLER_ENTITY_ENTITY_ID": true 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Smile\Seller\EntityCreator\MetadataPool 11 | 12 | 13 | 14 | 15 | 16 | Smile\Seller\EntityCreator\MetadataPool 17 | Smile\Seller\Model\Entity\CreationScopeResolver 18 | 19 | 20 | 21 | 22 | 23 | 24 | Smile\Seller\Model\ResourceModel\AttributePersistor 25 | 26 | 27 | 28 | 29 | 30 | 31 | seller 32 | seller 33 | 34 | jpg 35 | jpeg 36 | gif 37 | png 38 | pdf 39 | 40 | 41 | 42 | 43 | 44 | 45 | Smile\Seller\SellerImageUpload 46 | 47 | 48 | 49 | 50 | 51 | Smile\Seller\SellerImageUpload 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /etc/events.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/web/fonts/retailersuite/retailersuite.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smile-SA/magento2-module-seller/39257a76e0514becf5cf64cec18d455a803c66f6/lib/web/fonts/retailersuite/retailersuite.eot -------------------------------------------------------------------------------- /lib/web/fonts/retailersuite/retailersuite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lib/web/fonts/retailersuite/retailersuite.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smile-SA/magento2-module-seller/39257a76e0514becf5cf64cec18d455a803c66f6/lib/web/fonts/retailersuite/retailersuite.ttf -------------------------------------------------------------------------------- /lib/web/fonts/retailersuite/retailersuite.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smile-SA/magento2-module-seller/39257a76e0514becf5cf64cec18d455a803c66f6/lib/web/fonts/retailersuite/retailersuite.woff -------------------------------------------------------------------------------- /lib/web/fonts/retailersuite/retailersuite.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smile-SA/magento2-module-seller/39257a76e0514becf5cf64cec18d455a803c66f6/lib/web/fonts/retailersuite/retailersuite.woff2 -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | a:before { 12 | font-family: @retailersuite-icons-admin__font-name; 13 | content: "\e900"; 14 | } 15 | --------------------------------------------------------------------------------