CustomerComposite.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\CustomerImportExport\Model\Import;
  7. use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
  8. /**
  9. * Import entity customer combined model
  10. *
  11. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  12. */
  13. class CustomerComposite extends \Magento\ImportExport\Model\Import\AbstractEntity
  14. {
  15. /**#@+
  16. * Particular column names
  17. *
  18. * Names that begins with underscore is not an attribute. This name convention is for
  19. * to avoid interference with same attribute name.
  20. */
  21. const COLUMN_ADDRESS_PREFIX = '_address_';
  22. const COLUMN_DEFAULT_BILLING = '_address_default_billing_';
  23. const COLUMN_DEFAULT_SHIPPING = '_address_default_shipping_';
  24. /**#@-*/
  25. /**#@+
  26. * Data row scopes
  27. */
  28. const SCOPE_DEFAULT = 1;
  29. const SCOPE_ADDRESS = -1;
  30. /**#@-*/
  31. /**#@+
  32. * Component entity names
  33. */
  34. const COMPONENT_ENTITY_CUSTOMER = 'customer';
  35. const COMPONENT_ENTITY_ADDRESS = 'address';
  36. /**#@-*/
  37. /**
  38. * Error code for orphan rows
  39. */
  40. const ERROR_ROW_IS_ORPHAN = 'rowIsOrphan';
  41. /**
  42. * @var \Magento\CustomerImportExport\Model\Import\Customer
  43. */
  44. protected $_customerEntity;
  45. /**
  46. * @var \Magento\CustomerImportExport\Model\Import\Address
  47. */
  48. protected $_addressEntity;
  49. /**
  50. * Column names that holds values with particular meaning
  51. *
  52. * @var string[]
  53. */
  54. protected $_specialAttributes = [
  55. Customer::COLUMN_WEBSITE,
  56. Customer::COLUMN_STORE,
  57. self::COLUMN_DEFAULT_BILLING,
  58. self::COLUMN_DEFAULT_SHIPPING,
  59. ];
  60. /**
  61. * Permanent entity columns
  62. *
  63. * @var string[]
  64. */
  65. protected $_permanentAttributes = [
  66. Customer::COLUMN_EMAIL,
  67. Customer::COLUMN_WEBSITE,
  68. ];
  69. /**
  70. * Customer attributes
  71. *
  72. * @var string[]
  73. */
  74. protected $_customerAttributes = [];
  75. /**
  76. * Address attributes
  77. *
  78. * @var string[]
  79. */
  80. protected $_addressAttributes = [];
  81. /**
  82. * Website code of current customer row
  83. *
  84. * @var string
  85. */
  86. protected $_currentWebsiteCode;
  87. /**
  88. * Email of current customer
  89. *
  90. * @var string
  91. */
  92. protected $_currentEmail;
  93. /**
  94. * Next customer entity ID
  95. *
  96. * @var int
  97. */
  98. protected $_nextCustomerId;
  99. /**
  100. * DB data source models
  101. *
  102. * @var \Magento\ImportExport\Model\ResourceModel\Import\Data[]
  103. */
  104. protected $_dataSourceModels;
  105. /**
  106. * If we should check column names
  107. *
  108. * @var bool
  109. */
  110. protected $needColumnCheck = true;
  111. /**
  112. * Valid column names
  113. *
  114. * @array
  115. */
  116. protected $validColumnNames = [
  117. Customer::COLUMN_DEFAULT_BILLING,
  118. Customer::COLUMN_DEFAULT_SHIPPING,
  119. Customer::COLUMN_PASSWORD,
  120. ];
  121. /**
  122. * {@inheritdoc}
  123. */
  124. protected $masterAttributeCode = 'email';
  125. /**
  126. * @param \Magento\Framework\Stdlib\StringUtils $string
  127. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  128. * @param \Magento\ImportExport\Model\ImportFactory $importFactory
  129. * @param \Magento\ImportExport\Model\ResourceModel\Helper $resourceHelper
  130. * @param \Magento\Framework\App\ResourceConnection $resource
  131. * @param ProcessingErrorAggregatorInterface $errorAggregator
  132. * @param \Magento\CustomerImportExport\Model\ResourceModel\Import\CustomerComposite\DataFactory $dataFactory
  133. * @param \Magento\CustomerImportExport\Model\Import\CustomerFactory $customerFactory
  134. * @param \Magento\CustomerImportExport\Model\Import\AddressFactory $addressFactory
  135. * @param array $data
  136. * @throws \Magento\Framework\Exception\LocalizedException
  137. *
  138. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  139. */
  140. public function __construct(
  141. \Magento\Framework\Stdlib\StringUtils $string,
  142. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  143. \Magento\ImportExport\Model\ImportFactory $importFactory,
  144. \Magento\ImportExport\Model\ResourceModel\Helper $resourceHelper,
  145. \Magento\Framework\App\ResourceConnection $resource,
  146. ProcessingErrorAggregatorInterface $errorAggregator,
  147. \Magento\CustomerImportExport\Model\ResourceModel\Import\CustomerComposite\DataFactory $dataFactory,
  148. \Magento\CustomerImportExport\Model\Import\CustomerFactory $customerFactory,
  149. \Magento\CustomerImportExport\Model\Import\AddressFactory $addressFactory,
  150. array $data = []
  151. ) {
  152. parent::__construct($string, $scopeConfig, $importFactory, $resourceHelper, $resource, $errorAggregator, $data);
  153. $this->addMessageTemplate(
  154. self::ERROR_ROW_IS_ORPHAN,
  155. __('Orphan rows that will be skipped due default row errors')
  156. );
  157. $this->_availableBehaviors = [
  158. \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND,
  159. \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE,
  160. ];
  161. // customer entity stuff
  162. if (isset($data['customer_data_source_model'])) {
  163. $this->_dataSourceModels['customer'] = $data['customer_data_source_model'];
  164. } else {
  165. $arguments = [
  166. 'entity_type' => CustomerComposite::COMPONENT_ENTITY_CUSTOMER,
  167. ];
  168. $this->_dataSourceModels['customer'] = $dataFactory->create(['arguments' => $arguments]);
  169. }
  170. if (isset($data['customer_entity'])) {
  171. $this->_customerEntity = $data['customer_entity'];
  172. } else {
  173. $data['data_source_model'] = $this->_dataSourceModels['customer'];
  174. $this->_customerEntity = $customerFactory->create(['data' => $data]);
  175. unset($data['data_source_model']);
  176. }
  177. $this->_initCustomerAttributes();
  178. // address entity stuff
  179. if (isset($data['address_data_source_model'])) {
  180. $this->_dataSourceModels['address'] = $data['address_data_source_model'];
  181. } else {
  182. $arguments = [
  183. 'entity_type' => CustomerComposite::COMPONENT_ENTITY_ADDRESS,
  184. 'customer_attributes' => $this->_customerAttributes,
  185. ];
  186. $this->_dataSourceModels['address'] = $dataFactory->create(['arguments' => $arguments]);
  187. }
  188. if (isset($data['address_entity'])) {
  189. $this->_addressEntity = $data['address_entity'];
  190. } else {
  191. $data['data_source_model'] = $this->_dataSourceModels['address'];
  192. $this->_addressEntity = $addressFactory->create(['data' => $data]);
  193. unset($data['data_source_model']);
  194. }
  195. $this->_initAddressAttributes();
  196. // next customer id
  197. if (isset($data['next_customer_id'])) {
  198. $this->_nextCustomerId = $data['next_customer_id'];
  199. } else {
  200. $this->_nextCustomerId = $resourceHelper->getNextAutoincrement($this->_customerEntity->getEntityTable());
  201. }
  202. }
  203. /**
  204. * Collect customer attributes
  205. *
  206. * @return $this
  207. */
  208. protected function _initCustomerAttributes()
  209. {
  210. /** @var $attribute \Magento\Eav\Model\Entity\Attribute */
  211. foreach ($this->_customerEntity->getAttributeCollection() as $attribute) {
  212. $this->_customerAttributes[] = $attribute->getAttributeCode();
  213. }
  214. return $this;
  215. }
  216. /**
  217. * Collect address attributes
  218. *
  219. * @return $this
  220. */
  221. protected function _initAddressAttributes()
  222. {
  223. /** @var $attribute \Magento\Eav\Model\Entity\Attribute */
  224. foreach ($this->_addressEntity->getAttributeCollection() as $attribute) {
  225. $this->_addressAttributes[] = $attribute->getAttributeCode();
  226. }
  227. return $this;
  228. }
  229. /**
  230. * Import data rows
  231. *
  232. * @return bool
  233. */
  234. protected function _importData()
  235. {
  236. $result = $this->_customerEntity->importData();
  237. if ($this->getBehavior() != \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE) {
  238. return $result && $this->_addressEntity->setCustomerAttributes($this->_customerAttributes)->importData();
  239. }
  240. return $result;
  241. }
  242. /**
  243. * Imported entity type code getter
  244. *
  245. * @return string
  246. */
  247. public function getEntityTypeCode()
  248. {
  249. return 'customer_composite';
  250. }
  251. /**
  252. * @inheritDoc
  253. */
  254. public function validateData()
  255. {
  256. //Preparing both customer and address imports for mass validation.
  257. $source = $this->getSource();
  258. $this->_customerEntity->prepareCustomerData($source);
  259. $source->rewind();
  260. $rows = [];
  261. foreach ($source as $row) {
  262. $rows[] = [
  263. Address::COLUMN_EMAIL => $row[Customer::COLUMN_EMAIL] ?? null,
  264. Address::COLUMN_WEBSITE => $row[Customer::COLUMN_WEBSITE] ?? null
  265. ];
  266. }
  267. $source->rewind();
  268. $this->_addressEntity->prepareCustomerData($rows);
  269. return parent::validateData();
  270. }
  271. /**
  272. * Validate data row
  273. *
  274. * @param array $rowData
  275. * @param int $rowNumber
  276. * @return bool
  277. */
  278. public function validateRow(array $rowData, $rowNumber)
  279. {
  280. $rowScope = $this->_getRowScope($rowData);
  281. if ($rowScope == self::SCOPE_DEFAULT) {
  282. if ($this->_customerEntity->validateRow($rowData, $rowNumber)) {
  283. $this->_currentWebsiteCode =
  284. $rowData[Customer::COLUMN_WEBSITE];
  285. $this->_currentEmail = strtolower(
  286. $rowData[Customer::COLUMN_EMAIL]
  287. );
  288. // Add new customer data into customer storage for address entity instance
  289. $websiteId = $this->_customerEntity->getWebsiteId($this->_currentWebsiteCode);
  290. if (!$this->_addressEntity->getCustomerStorage()->getCustomerId($this->_currentEmail, $websiteId)) {
  291. $this->_addressEntity->getCustomerStorage()->addCustomerByArray(
  292. [
  293. 'entity_id' => $this->_nextCustomerId,
  294. 'email' => $this->_currentEmail,
  295. 'website_id' => $websiteId,
  296. ]
  297. );
  298. $this->_nextCustomerId++;
  299. }
  300. return $this->_validateAddressRow($rowData, $rowNumber);
  301. } else {
  302. $this->_currentWebsiteCode = null;
  303. $this->_currentEmail = null;
  304. }
  305. } else {
  306. if (!empty($this->_currentWebsiteCode) && !empty($this->_currentEmail)) {
  307. return $this->_validateAddressRow($rowData, $rowNumber);
  308. } else {
  309. $this->addRowError(self::ERROR_ROW_IS_ORPHAN, $rowNumber);
  310. }
  311. }
  312. return false;
  313. }
  314. /**
  315. * Validate address row
  316. *
  317. * @param array $rowData
  318. * @param int $rowNumber
  319. * @return bool
  320. */
  321. protected function _validateAddressRow(array $rowData, $rowNumber)
  322. {
  323. if ($this->getBehavior() == \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE) {
  324. return true;
  325. }
  326. $rowData = $this->_prepareAddressRowData($rowData);
  327. if (empty($rowData)) {
  328. return true;
  329. } else {
  330. $rowData[Address::COLUMN_WEBSITE] =
  331. $this->_currentWebsiteCode;
  332. $rowData[Address::COLUMN_EMAIL] =
  333. $this->_currentEmail;
  334. $rowData[Address::COLUMN_ADDRESS_ID] = null;
  335. return $this->_addressEntity->validateRow($rowData, $rowNumber);
  336. }
  337. }
  338. /**
  339. * Prepare data row for address entity validation or import
  340. *
  341. * @param array $rowData
  342. * @return array
  343. */
  344. protected function _prepareAddressRowData(array $rowData)
  345. {
  346. $excludedAttributes = [self::COLUMN_DEFAULT_BILLING, self::COLUMN_DEFAULT_SHIPPING];
  347. unset(
  348. $rowData[Customer::COLUMN_WEBSITE],
  349. $rowData[Customer::COLUMN_STORE]
  350. );
  351. $result = [];
  352. foreach ($rowData as $key => $value) {
  353. if (!in_array($key, $this->_customerAttributes) && !empty($value)) {
  354. if (!in_array($key, $excludedAttributes)) {
  355. $key = str_replace(self::COLUMN_ADDRESS_PREFIX, '', $key);
  356. }
  357. $result[$key] = $value;
  358. }
  359. }
  360. return $result;
  361. }
  362. /**
  363. * Obtain scope of the row from row data
  364. *
  365. * @param array $rowData
  366. * @return int
  367. */
  368. protected function _getRowScope(array $rowData)
  369. {
  370. if (!isset($rowData[Customer::COLUMN_EMAIL])) {
  371. return self::SCOPE_ADDRESS;
  372. }
  373. return strlen(
  374. trim($rowData[Customer::COLUMN_EMAIL])
  375. ) ? self::SCOPE_DEFAULT : self::SCOPE_ADDRESS;
  376. }
  377. /**
  378. * Set data from outside to change behavior
  379. *
  380. * @param array $parameters
  381. * @return $this
  382. */
  383. public function setParameters(array $parameters)
  384. {
  385. parent::setParameters($parameters);
  386. if ($this->getBehavior() == \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND) {
  387. $parameters['behavior'] = \Magento\ImportExport\Model\Import::BEHAVIOR_ADD_UPDATE;
  388. }
  389. $this->_customerEntity->setParameters($parameters);
  390. $this->_addressEntity->setParameters($parameters);
  391. return $this;
  392. }
  393. /**
  394. * Source model setter
  395. *
  396. * @param \Magento\ImportExport\Model\Import\AbstractSource $source
  397. * @return \Magento\ImportExport\Model\Import\AbstractEntity
  398. */
  399. public function setSource(\Magento\ImportExport\Model\Import\AbstractSource $source)
  400. {
  401. $this->_customerEntity->setSource($source);
  402. $this->_addressEntity->setSource($source);
  403. return parent::setSource($source);
  404. }
  405. /**
  406. * Returns number of checked entities
  407. *
  408. * @return int
  409. */
  410. public function getProcessedEntitiesCount()
  411. {
  412. return $this->_customerEntity->getProcessedEntitiesCount() +
  413. $this->_addressEntity->getProcessedEntitiesCount();
  414. }
  415. /**
  416. * Is attribute contains particular data (not plain customer attribute)
  417. *
  418. * @param string $attributeCode
  419. * @return bool
  420. */
  421. public function isAttributeParticular($attributeCode)
  422. {
  423. if (in_array(str_replace(self::COLUMN_ADDRESS_PREFIX, '', $attributeCode), $this->_addressAttributes)) {
  424. return true;
  425. } else {
  426. return parent::isAttributeParticular($attributeCode);
  427. }
  428. }
  429. /**
  430. * Prepare validated row data for saving to db
  431. *
  432. * @param array $rowData
  433. * @return array
  434. */
  435. protected function _prepareRowForDb(array $rowData)
  436. {
  437. $rowData['_scope'] = $this->_getRowScope($rowData);
  438. $rowData[Address::COLUMN_WEBSITE] =
  439. $this->_currentWebsiteCode;
  440. $rowData[Address::COLUMN_EMAIL] = $this->_currentEmail;
  441. $rowData[Address::COLUMN_ADDRESS_ID] = null;
  442. return parent::_prepareRowForDb($rowData);
  443. }
  444. /**
  445. * @inheritDoc
  446. */
  447. public function getValidColumnNames()
  448. {
  449. return array_unique(
  450. array_merge(
  451. $this->validColumnNames,
  452. $this->_customerAttributes,
  453. $this->_addressAttributes,
  454. $this->_customerEntity->getValidColumnNames()
  455. )
  456. );
  457. }
  458. }