_resourceHelper = $resourceHelper; if (isset($data['attribute_collection'])) { $this->_attributeCollection = $data['attribute_collection']; unset($data['attribute_collection']); } else { $this->_attributeCollection = $attrCollectionFactory->create(); $this->_attributeCollection->addSystemHiddenFilterWithPasswordHash(); $data['attribute_collection'] = $this->_attributeCollection; } parent::__construct( $string, $scopeConfig, $importFactory, $resourceHelper, $resource, $errorAggregator, $storeManager, $collectionFactory, $eavConfig, $storageFactory, $data ); $this->_specialAttributes[] = self::COLUMN_WEBSITE; $this->_specialAttributes[] = self::COLUMN_STORE; $this->_permanentAttributes[] = self::COLUMN_EMAIL; $this->_permanentAttributes[] = self::COLUMN_WEBSITE; $this->_indexValueAttributes[] = 'group_id'; $this->addMessageTemplate( self::ERROR_DUPLICATE_EMAIL_SITE, __('This email is found more than once in the import file.') ); $this->addMessageTemplate( self::ERROR_ROW_IS_ORPHAN, __('Orphan rows that will be skipped due default row errors') ); $this->addMessageTemplate( self::ERROR_INVALID_STORE, __('Invalid value in Store column (store does not exists?)') ); $this->addMessageTemplate( self::ERROR_EMAIL_SITE_NOT_FOUND, __('We can\'t find that email and website combination.') ); $this->addMessageTemplate(self::ERROR_PASSWORD_LENGTH, __('Please enter a password with a valid length.')); $this->_initStores(true)->_initAttributes(); $this->_customerModel = $customerFactory->create(); /** @var $customerResource \Magento\Customer\Model\ResourceModel\Customer */ $customerResource = $this->_customerModel->getResource(); $this->_entityTable = $customerResource->getEntityTable(); } /** * Update and insert data in entity table * * @param array $entitiesToCreate Rows for insert * @param array $entitiesToUpdate Rows for update * @return $this */ protected function _saveCustomerEntities(array $entitiesToCreate, array $entitiesToUpdate) { if ($entitiesToCreate) { $this->_connection->insertMultiple($this->_entityTable, $entitiesToCreate); } if ($entitiesToUpdate) { $this->_connection->insertOnDuplicate( $this->_entityTable, $entitiesToUpdate, $this->getCustomerEntityFieldsToUpdate($entitiesToUpdate) ); } return $this; } /** * Filter the entity that are being updated so we only change fields found in the importer file * * @param array $entitiesToUpdate * @return array */ private function getCustomerEntityFieldsToUpdate(array $entitiesToUpdate): array { $firstCustomer = reset($entitiesToUpdate); $columnsToUpdate = array_keys($firstCustomer); $customerFieldsToUpdate = array_filter($this->customerFields, function ($field) use ($columnsToUpdate) { return in_array($field, $columnsToUpdate); }); return $customerFieldsToUpdate; } /** * Save customer attributes. * * @param array $attributesData * @return $this */ protected function _saveCustomerAttributes(array $attributesData) { foreach ($attributesData as $tableName => $data) { $tableData = []; foreach ($data as $customerId => $attributeData) { foreach ($attributeData as $attributeId => $value) { $tableData[] = [ 'entity_id' => $customerId, 'attribute_id' => $attributeId, 'value' => $value, ]; } } $this->_connection->insertOnDuplicate($tableName, $tableData, ['value']); } return $this; } /** * Delete list of customers * * @param array $entitiesToDelete customers id list * @return $this */ protected function _deleteCustomerEntities(array $entitiesToDelete) { $condition = $this->_connection->quoteInto('entity_id IN (?)', $entitiesToDelete); $this->_connection->delete($this->_entityTable, $condition); return $this; } /** * Retrieve next customer entity id * * @return int */ protected function _getNextEntityId() { if (!$this->_nextEntityId) { $this->_nextEntityId = $this->_resourceHelper->getNextAutoincrement($this->_entityTable); } return $this->_nextEntityId++; } /** * Prepare customers data for existing customers checks to perform mass validation/import efficiently. * * @param array|AbstractSource $rows * * @return void * @since 100.2.3 */ public function prepareCustomerData($rows): void { $customersPresent = []; foreach ($rows as $rowData) { $email = $rowData[static::COLUMN_EMAIL] ?? null; $websiteId = isset($rowData[static::COLUMN_WEBSITE]) ? $this->getWebsiteId($rowData[static::COLUMN_WEBSITE]) : false; if ($email && $websiteId !== false) { $customersPresent[] = [ 'email' => $email, 'website_id' => $websiteId, ]; } } $this->getCustomerStorage()->prepareCustomers($customersPresent); } /** * @inheritDoc * @since 100.2.3 */ public function validateData() { $this->prepareCustomerData($this->getSource()); return parent::validateData(); } /** * Prepare customer data for update * * @param array $rowData * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _prepareDataForUpdate(array $rowData) { $multiSeparator = $this->getMultipleValueSeparator(); $entitiesToCreate = []; $entitiesToUpdate = []; $attributesToSave = []; // entity table data $now = new \DateTime(); if (empty($rowData['created_at'])) { $createdAt = $now; } else { $createdAt = (new \DateTime())->setTimestamp(strtotime($rowData['created_at'])); } $emailInLowercase = strtolower($rowData[self::COLUMN_EMAIL]); $newCustomer = false; $entityId = $this->_getCustomerId($emailInLowercase, $rowData[self::COLUMN_WEBSITE]); if (!$entityId) { // create $newCustomer = true; $entityId = $this->_getNextEntityId(); $this->_newCustomers[$emailInLowercase][$rowData[self::COLUMN_WEBSITE]] = $entityId; } // password change/set if (isset($rowData['password']) && strlen($rowData['password'])) { $rowData['password_hash'] = $this->_customerModel->hashPassword($rowData['password']); } $entityRow = ['entity_id' => $entityId]; // attribute values foreach (array_intersect_key($rowData, $this->_attributes) as $attributeCode => $value) { $attributeParameters = $this->_attributes[$attributeCode]; if (in_array($attributeParameters['type'], ['select', 'boolean'])) { $value = $this->getSelectAttrIdByValue($attributeParameters, $value); } elseif ('multiselect' == $attributeParameters['type']) { $ids = []; foreach (explode($multiSeparator, mb_strtolower($value)) as $subValue) { $ids[] = $this->getSelectAttrIdByValue($attributeParameters, $subValue); } $value = implode(',', $ids); } elseif ('datetime' == $attributeParameters['type'] && !empty($value)) { $value = (new \DateTime())->setTimestamp(strtotime($value)); $value = $value->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); } if (!$this->_attributes[$attributeCode]['is_static']) { /** @var $attribute \Magento\Customer\Model\Attribute */ $attribute = $this->_customerModel->getAttribute($attributeCode); $backendModel = $attribute->getBackendModel(); if ($backendModel && $attribute->getFrontendInput() != 'select' && $attribute->getFrontendInput() != 'datetime') { $attribute->getBackend()->beforeSave($this->_customerModel->setData($attributeCode, $value)); $value = $this->_customerModel->getData($attributeCode); } $attributesToSave[$attribute->getBackend() ->getTable()][$entityId][$attributeParameters['id']] = $value; // restore 'backend_model' to avoid default setting $attribute->setBackendModel($backendModel); } else { $entityRow[$attributeCode] = $value; } } if ($newCustomer) { // create $entityRow['group_id'] = empty($rowData['group_id']) ? self::DEFAULT_GROUP_ID : $rowData['group_id']; $entityRow['store_id'] = empty($rowData[self::COLUMN_STORE]) ? \Magento\Store\Model\Store::DEFAULT_STORE_ID : $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; $entityRow['created_at'] = $createdAt->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); $entityRow['updated_at'] = $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); $entityRow['website_id'] = $this->_websiteCodeToId[$rowData[self::COLUMN_WEBSITE]]; $entityRow['email'] = $emailInLowercase; $entityRow['is_active'] = 1; $entitiesToCreate[] = $entityRow; } else { // edit $entityRow['updated_at'] = $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); if (!empty($rowData[self::COLUMN_STORE])) { $entityRow['store_id'] = $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; } $entitiesToUpdate[] = $entityRow; } return [ self::ENTITIES_TO_CREATE_KEY => $entitiesToCreate, self::ENTITIES_TO_UPDATE_KEY => $entitiesToUpdate, self::ATTRIBUTES_TO_SAVE_KEY => $attributesToSave ]; } /** * Import data rows * * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _importData() { while ($bunch = $this->_dataSourceModel->getNextBunch()) { $this->prepareCustomerData($bunch); $entitiesToCreate = []; $entitiesToUpdate = []; $entitiesToDelete = []; $attributesToSave = []; foreach ($bunch as $rowNumber => $rowData) { if (!$this->validateRow($rowData, $rowNumber)) { continue; } if ($this->getErrorAggregator()->hasToBeTerminated()) { $this->getErrorAggregator()->addRowToSkip($rowNumber); continue; } if ($this->getBehavior($rowData) == \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE) { $entitiesToDelete[] = $this->_getCustomerId( $rowData[self::COLUMN_EMAIL], $rowData[self::COLUMN_WEBSITE] ); } elseif ($this->getBehavior($rowData) == \Magento\ImportExport\Model\Import::BEHAVIOR_ADD_UPDATE) { $processedData = $this->_prepareDataForUpdate($rowData); $entitiesToCreate = array_merge($entitiesToCreate, $processedData[self::ENTITIES_TO_CREATE_KEY]); $entitiesToUpdate = array_merge($entitiesToUpdate, $processedData[self::ENTITIES_TO_UPDATE_KEY]); foreach ($processedData[self::ATTRIBUTES_TO_SAVE_KEY] as $tableName => $customerAttributes) { if (!isset($attributesToSave[$tableName])) { $attributesToSave[$tableName] = []; } $attributesToSave[$tableName] = array_diff_key( $attributesToSave[$tableName], $customerAttributes ) + $customerAttributes; } } } $this->updateItemsCounterStats($entitiesToCreate, $entitiesToUpdate, $entitiesToDelete); /** * Save prepared data */ if ($entitiesToCreate || $entitiesToUpdate) { $this->_saveCustomerEntities($entitiesToCreate, $entitiesToUpdate); } if ($attributesToSave) { $this->_saveCustomerAttributes($attributesToSave); } if ($entitiesToDelete) { $this->_deleteCustomerEntities($entitiesToDelete); } } return true; } /** * EAV entity type code getter * * @return string */ public function getEntityTypeCode() { return $this->_attributeCollection->getEntityTypeCode(); } /** * Validate row data for add/update behaviour * * @param array $rowData * @param int $rowNumber * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _validateRowForUpdate(array $rowData, $rowNumber) { if ($this->_checkUniqueKey($rowData, $rowNumber)) { $email = strtolower($rowData[self::COLUMN_EMAIL]); $website = $rowData[self::COLUMN_WEBSITE]; if (isset($this->_newCustomers[strtolower($rowData[self::COLUMN_EMAIL])][$website])) { $this->addRowError(self::ERROR_DUPLICATE_EMAIL_SITE, $rowNumber); } $this->_newCustomers[$email][$website] = false; if (!empty($rowData[self::COLUMN_STORE]) && !isset($this->_storeCodeToId[$rowData[self::COLUMN_STORE]])) { $this->addRowError(self::ERROR_INVALID_STORE, $rowNumber); } // check password if (isset( $rowData['password'] ) && strlen( $rowData['password'] ) && $this->string->strlen( $rowData['password'] ) < self::MIN_PASSWORD_LENGTH ) { $this->addRowError(self::ERROR_PASSWORD_LENGTH, $rowNumber); } // check simple attributes foreach ($this->_attributes as $attributeCode => $attributeParams) { if (in_array($attributeCode, $this->_ignoredAttributes)) { continue; } $isFieldRequired = $attributeParams['is_required']; $isFieldNotSetAndCustomerDoesNotExist = !isset($rowData[$attributeCode]) && !$this->_getCustomerId($email, $website); $isFieldSetAndTrimmedValueIsEmpty = isset($rowData[$attributeCode]) && '' === trim($rowData[$attributeCode]); if ($isFieldRequired && ($isFieldNotSetAndCustomerDoesNotExist || $isFieldSetAndTrimmedValueIsEmpty)) { $this->addRowError(self::ERROR_VALUE_IS_REQUIRED, $rowNumber, $attributeCode); continue; } if (isset($rowData[$attributeCode]) && strlen($rowData[$attributeCode])) { $this->isAttributeValid( $attributeCode, $attributeParams, $rowData, $rowNumber, isset($this->_parameters[Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR]) ? $this->_parameters[Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR] : Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR ); } } } } /** * Validate row data for delete behaviour * * @param array $rowData * @param int $rowNumber * @return void */ protected function _validateRowForDelete(array $rowData, $rowNumber) { if ($this->_checkUniqueKey($rowData, $rowNumber)) { if (!$this->_getCustomerId($rowData[self::COLUMN_EMAIL], $rowData[self::COLUMN_WEBSITE])) { $this->addRowError(self::ERROR_CUSTOMER_NOT_FOUND, $rowNumber); } } } /** * Entity table name getter * * @return string */ public function getEntityTable() { return $this->_entityTable; } /** * @inheritDoc */ public function getValidColumnNames() { return array_unique( array_merge( $this->validColumnNames, $this->customerFields ) ); } }