PageRenderTime 39ms CodeModel.GetById 1ms RepoModel.GetById 1ms app.codeStats 0ms

/app/code/core/Mage/ImportExport/Model/Import/Entity/Customer.php

https://bitbucket.org/claudiu_marginean/magento-hg-mirror
PHP | 611 lines | 315 code | 53 blank | 243 comment | 51 complexity | d345b8c398beb389c4774cc46a03f2df MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Mage
  22. * @package Mage_ImportExport
  23. * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * Import entity customer model
  28. *
  29. * @category Mage
  30. * @package Mage_ImportExport
  31. * @author Magento Core Team <core@magentocommerce.com>
  32. */
  33. class Mage_ImportExport_Model_Import_Entity_Customer extends Mage_ImportExport_Model_Import_Entity_Abstract
  34. {
  35. /**
  36. * Size of bunch - part of entities to save in one step.
  37. */
  38. const BUNCH_SIZE = 20;
  39. /**
  40. * Data row scopes.
  41. */
  42. const SCOPE_DEFAULT = 1;
  43. const SCOPE_ADDRESS = -1;
  44. /**
  45. * Permanent column names.
  46. *
  47. * Names that begins with underscore is not an attribute. This name convention is for
  48. * to avoid interference with same attribute name.
  49. */
  50. const COL_EMAIL = 'email';
  51. const COL_WEBSITE = '_website';
  52. const COL_STORE = '_store';
  53. /**
  54. * Error codes.
  55. */
  56. const ERROR_INVALID_WEBSITE = 'invalidWebsite';
  57. const ERROR_INVALID_EMAIL = 'invalidEmail';
  58. const ERROR_DUPLICATE_EMAIL_SITE = 'duplicateEmailSite';
  59. const ERROR_EMAIL_IS_EMPTY = 'emailIsEmpty';
  60. const ERROR_ROW_IS_ORPHAN = 'rowIsOrphan';
  61. const ERROR_VALUE_IS_REQUIRED = 'valueIsRequired';
  62. const ERROR_INVALID_STORE = 'invalidStore';
  63. const ERROR_EMAIL_SITE_NOT_FOUND = 'emailSiteNotFound';
  64. const ERROR_PASSWORD_LENGTH = 'passwordLength';
  65. /**
  66. * Customer constants
  67. *
  68. */
  69. const DEFAULT_GROUP_ID = 1;
  70. const MAX_PASSWD_LENGTH = 6;
  71. /**
  72. * Customer address import entity model.
  73. *
  74. * @var Mage_ImportExport_Model_Import_Entity_Customer_Address
  75. */
  76. protected $_addressEntity;
  77. /**
  78. * Customer attributes parameters.
  79. *
  80. * [attr_code_1] => array(
  81. * 'options' => array(),
  82. * 'type' => 'text', 'price', 'textarea', 'select', etc.
  83. * 'id' => ..
  84. * ),
  85. * ...
  86. *
  87. * @var array
  88. */
  89. protected $_attributes = array();
  90. /**
  91. * Customer account sharing. TRUE - is global, FALSE - is per website.
  92. *
  93. * @var boolean
  94. */
  95. protected $_customerGlobal;
  96. /**
  97. * Customer groups ID-to-name.
  98. *
  99. * @var array
  100. */
  101. protected $_customerGroups = array();
  102. /**
  103. * Customer entity DB table name.
  104. *
  105. * @var string
  106. */
  107. protected $_entityTable;
  108. /**
  109. * Array of attribute codes which will be ignored in validation and import procedures.
  110. * For example, when entity attribute has own validation and import procedures
  111. * or just to deny this attribute processing.
  112. *
  113. * @var array
  114. */
  115. protected $_ignoredAttributes = array('website_id', 'store_id', 'default_billing', 'default_shipping');
  116. /**
  117. * Attributes with index (not label) value.
  118. *
  119. * @var array
  120. */
  121. protected $_indexValueAttributes = array('group_id');
  122. /**
  123. * Validation failure message template definitions
  124. *
  125. * @var array
  126. */
  127. protected $_messageTemplates = array(
  128. self::ERROR_INVALID_WEBSITE => 'Invalid value in Website column (website does not exists?)',
  129. self::ERROR_INVALID_EMAIL => 'E-mail is invalid',
  130. self::ERROR_DUPLICATE_EMAIL_SITE => 'E-mail is duplicated in import file',
  131. self::ERROR_EMAIL_IS_EMPTY => 'E-mail is not specified',
  132. self::ERROR_ROW_IS_ORPHAN => 'Orphan rows that will be skipped due default row errors',
  133. self::ERROR_VALUE_IS_REQUIRED => "Required attribute '%s' has an empty value",
  134. self::ERROR_INVALID_STORE => 'Invalid value in Store column (store does not exists?)',
  135. self::ERROR_EMAIL_SITE_NOT_FOUND => 'E-mail and website combination is not found',
  136. self::ERROR_PASSWORD_LENGTH => 'Invalid password length'
  137. );
  138. /**
  139. * Dry-runned customers information from import file.
  140. *
  141. * @var array
  142. */
  143. protected $_newCustomers = array();
  144. /**
  145. * Existing customers information. In form of:
  146. *
  147. * [customer e-mail] => array(
  148. * [website code 1] => customer_id 1,
  149. * [website code 2] => customer_id 2,
  150. * ... => ... ,
  151. * [website code n] => customer_id n,
  152. * )
  153. *
  154. * @var array
  155. */
  156. protected $_oldCustomers = array();
  157. /**
  158. * Column names that holds values with particular meaning.
  159. *
  160. * @var array
  161. */
  162. protected $_particularAttributes = array(self::COL_WEBSITE, self::COL_STORE);
  163. /**
  164. * Permanent entity columns.
  165. *
  166. * @var array
  167. */
  168. protected $_permanentAttributes = array(self::COL_EMAIL, self::COL_WEBSITE);
  169. /**
  170. * All stores code-ID pairs.
  171. *
  172. * @var array
  173. */
  174. protected $_storeCodeToId = array();
  175. /**
  176. * Website code-to-ID
  177. *
  178. * @var array
  179. */
  180. protected $_websiteCodeToId = array();
  181. /**
  182. * Website ID-to-code
  183. *
  184. * @var array
  185. */
  186. protected $_websiteIdToCode = array();
  187. /**
  188. * Constructor.
  189. *
  190. * @return void
  191. */
  192. public function __construct()
  193. {
  194. parent::__construct();
  195. $this->_initWebsites()
  196. ->_initStores()
  197. ->_initCustomerGroups()
  198. ->_initAttributes()
  199. ->_initCustomers();
  200. $this->_entityTable = Mage::getModel('customer/customer')->getResource()->getEntityTable();
  201. $this->_addressEntity = Mage::getModel('importexport/import_entity_customer_address', $this);
  202. }
  203. /**
  204. * Delete customers.
  205. *
  206. * @return Mage_ImportExport_Model_Import_Entity_Customer
  207. */
  208. protected function _deleteCustomers()
  209. {
  210. while ($bunch = $this->_dataSourceModel->getNextBunch()) {
  211. $idToDelete = array();
  212. foreach ($bunch as $rowNum => $rowData) {
  213. if (self::SCOPE_DEFAULT == $this->getRowScope($rowData) && $this->validateRow($rowData, $rowNum)) {
  214. $idToDelete[] = $this->_oldCustomers[$rowData[self::COL_EMAIL]][$rowData[self::COL_WEBSITE]];
  215. }
  216. }
  217. if ($idToDelete) {
  218. $this->_connection->query(
  219. $this->_connection->quoteInto(
  220. "DELETE FROM `{$this->_entityTable}` WHERE `entity_id` IN (?)", $idToDelete
  221. )
  222. );
  223. }
  224. }
  225. return $this;
  226. }
  227. /**
  228. * Save customer data to DB.
  229. *
  230. * @throws Exception
  231. * @return bool Result of operation.
  232. */
  233. protected function _importData()
  234. {
  235. if (Mage_ImportExport_Model_Import::BEHAVIOR_DELETE == $this->getBehavior()) {
  236. $this->_deleteCustomers();
  237. } else {
  238. $this->_saveCustomers();
  239. $this->_addressEntity->importData();
  240. }
  241. return true;
  242. }
  243. /**
  244. * Initialize customer attributes.
  245. *
  246. * @return Mage_ImportExport_Model_Import_Entity_Customer
  247. */
  248. protected function _initAttributes()
  249. {
  250. $collection = Mage::getResourceModel('customer/attribute_collection')->addSystemHiddenFilterWithPasswordHash();
  251. foreach ($collection as $attribute) {
  252. $this->_attributes[$attribute->getAttributeCode()] = array(
  253. 'id' => $attribute->getId(),
  254. 'is_required' => $attribute->getIsRequired(),
  255. 'is_static' => $attribute->isStatic(),
  256. 'rules' => $attribute->getValidateRules() ? unserialize($attribute->getValidateRules()) : null,
  257. 'type' => Mage_ImportExport_Model_Import::getAttributeType($attribute),
  258. 'options' => $this->getAttributeOptions($attribute)
  259. );
  260. }
  261. return $this;
  262. }
  263. /**
  264. * Initialize customer groups.
  265. *
  266. * @return Mage_ImportExport_Model_Import_Entity_Customer
  267. */
  268. protected function _initCustomerGroups()
  269. {
  270. foreach (Mage::getResourceModel('customer/group_collection') as $customerGroup) {
  271. $this->_customerGroups[$customerGroup->getId()] = true;
  272. }
  273. return $this;
  274. }
  275. /**
  276. * Initialize existent customers data.
  277. *
  278. * @return Mage_ImportExport_Model_Import_Entity_Customer
  279. */
  280. protected function _initCustomers()
  281. {
  282. foreach (Mage::getResourceModel('customer/customer_collection') as $customer) {
  283. $email = $customer->getEmail();
  284. if (!isset($this->_oldCustomers[$email])) {
  285. $this->_oldCustomers[$email] = array();
  286. }
  287. $this->_oldCustomers[$email][$this->_websiteIdToCode[$customer->getWebsiteId()]] = $customer->getId();
  288. }
  289. $this->_customerGlobal = Mage::getModel('customer/customer')->getSharingConfig()->isGlobalScope();
  290. return $this;
  291. }
  292. /**
  293. * Initialize stores hash.
  294. *
  295. * @return Mage_ImportExport_Model_Import_Entity_Customer
  296. */
  297. protected function _initStores()
  298. {
  299. foreach (Mage::app()->getStores(true) as $store) {
  300. $this->_storeCodeToId[$store->getCode()] = $store->getId();
  301. }
  302. return $this;
  303. }
  304. /**
  305. * Initialize website values.
  306. *
  307. * @return Mage_ImportExport_Model_Import_Entity_Customer
  308. */
  309. protected function _initWebsites()
  310. {
  311. /** @var $website Mage_Core_Model_Website */
  312. foreach (Mage::app()->getWebsites(true) as $website) {
  313. $this->_websiteCodeToId[$website->getCode()] = $website->getId();
  314. $this->_websiteIdToCode[$website->getId()] = $website->getCode();
  315. }
  316. return $this;
  317. }
  318. /**
  319. * Gather and save information about customer entities.
  320. *
  321. * @return Mage_ImportExport_Model_Import_Entity_Customer
  322. */
  323. protected function _saveCustomers()
  324. {
  325. /** @var $resource Mage_Customer_Model_Customer */
  326. $resource = Mage::getModel('customer/customer');
  327. $strftimeFormat = Varien_Date::convertZendToStrftime(Varien_Date::DATETIME_INTERNAL_FORMAT, true, true);
  328. $nextEntityId = $this->getNextAutoincrement($resource->getResource()->getEntityTable());
  329. $passId = $resource->getAttribute('password_hash')->getId();
  330. $passTable = $resource->getAttribute('password_hash')->getBackend()->getTable();
  331. while ($bunch = $this->_dataSourceModel->getNextBunch()) {
  332. $entityRowsIn = array();
  333. $entityRowsUp = array();
  334. $attributes = array();
  335. foreach ($bunch as $rowNum => $rowData) {
  336. if (!$this->validateRow($rowData, $rowNum)) {
  337. continue;
  338. }
  339. if (self::SCOPE_DEFAULT == $this->getRowScope($rowData)) {
  340. // entity table data
  341. $entityRow = array(
  342. 'group_id' => empty($rowData['group_id']) ? self::DEFAULT_GROUP_ID : $rowData['group_id'],
  343. 'store_id' => empty($rowData[self::COL_STORE])
  344. ? 0 : $this->_storeCodeToId[$rowData[self::COL_STORE]],
  345. 'created_at' => empty($rowData['created_at'])
  346. ? now() : gmstrftime($strftimeFormat, strtotime($rowData['created_at'])),
  347. 'updated_at' => now()
  348. );
  349. if (isset($this->_oldCustomers[$rowData[self::COL_EMAIL]][$rowData[self::COL_WEBSITE]])) { // edit
  350. $entityId = $this->_oldCustomers[$rowData[self::COL_EMAIL]][$rowData[self::COL_WEBSITE]];
  351. $entityRow['entity_id'] = $entityId;
  352. $entityRowsUp[] = $entityRow;
  353. } else { // create
  354. $entityId = $nextEntityId++;
  355. $entityRow['entity_id'] = $entityId;
  356. $entityRow['entity_type_id'] = $this->_entityTypeId;
  357. $entityRow['attribute_set_id'] = 0;
  358. $entityRow['website_id'] = $this->_websiteCodeToId[$rowData[self::COL_WEBSITE]];
  359. $entityRow['email'] = $rowData[self::COL_EMAIL];
  360. $entityRow['is_active'] = 1;
  361. $entityRowsIn[] = $entityRow;
  362. $this->_newCustomers[$rowData[self::COL_EMAIL]][$rowData[self::COL_WEBSITE]] = $entityId;
  363. }
  364. // attribute values
  365. foreach (array_intersect_key($rowData, $this->_attributes) as $attrCode => $value) {
  366. if (!$this->_attributes[$attrCode]['is_static'] && strlen($value)) {
  367. /** @var $attribute Mage_Customer_Model_Attribute */
  368. $attribute = $resource->getAttribute($attrCode);
  369. $backModel = $attribute->getBackendModel();
  370. $attrParams = $this->_attributes[$attrCode];
  371. if ('select' == $attrParams['type']) {
  372. $value = $attrParams['options'][strtolower($value)];
  373. } elseif ('datetime' == $attrParams['type']) {
  374. $value = gmstrftime($strftimeFormat, strtotime($value));
  375. } elseif ($backModel) {
  376. $attribute->getBackend()->beforeSave($resource->setData($attrCode, $value));
  377. $value = $resource->getData($attrCode);
  378. }
  379. $attributes[$attribute->getBackend()->getTable()][$entityId][$attrParams['id']] = $value;
  380. // restore 'backend_model' to avoid default setting
  381. $attribute->setBackendModel($backModel);
  382. }
  383. }
  384. // password change/set
  385. if (isset($rowData['password']) && strlen($rowData['password'])) {
  386. $attributes[$passTable][$entityId][$passId] = $resource->hashPassword($rowData['password']);
  387. }
  388. }
  389. }
  390. $this->_saveCustomerEntity($entityRowsIn, $entityRowsUp)->_saveCustomerAttributes($attributes);
  391. }
  392. return $this;
  393. }
  394. /**
  395. * Save customer attributes.
  396. *
  397. * @param array $attributesData
  398. * @return Mage_ImportExport_Model_Import_Entity_Customer
  399. */
  400. protected function _saveCustomerAttributes(array $attributesData)
  401. {
  402. foreach ($attributesData as $tableName => $data) {
  403. $tableData = array();
  404. foreach ($data as $customerId => $attrData) {
  405. foreach ($attrData as $attributeId => $value) {
  406. $tableData[] = array(
  407. 'entity_id' => $customerId,
  408. 'entity_type_id' => $this->_entityTypeId,
  409. 'attribute_id' => $attributeId,
  410. 'value' => $value
  411. );
  412. }
  413. }
  414. $this->_connection->insertOnDuplicate($tableName, $tableData, array('value'));
  415. }
  416. return $this;
  417. }
  418. /**
  419. * Update and insert data in entity table.
  420. *
  421. * @param array $entityRowsIn Row for insert
  422. * @param array $entityRowsUp Row for update
  423. * @return Mage_ImportExport_Model_Import_Entity_Customer
  424. */
  425. protected function _saveCustomerEntity(array $entityRowsIn, array $entityRowsUp)
  426. {
  427. if ($entityRowsIn) {
  428. $this->_connection->insertMultiple($this->_entityTable, $entityRowsIn);
  429. }
  430. if ($entityRowsUp) {
  431. $this->_connection->insertOnDuplicate(
  432. $this->_entityTable,
  433. $entityRowsUp,
  434. array('group_id', 'store_id', 'updated_at', 'created_at')
  435. );
  436. }
  437. return $this;
  438. }
  439. /**
  440. * Get customer ID. Method tries to find ID from old and new customers. If it fails - it returns NULL.
  441. *
  442. * @param string $email
  443. * @param string $websiteCode
  444. * @return string|null
  445. */
  446. public function getCustomerId($email, $websiteCode)
  447. {
  448. if (isset($this->_oldCustomers[$email][$websiteCode])) {
  449. return $this->_oldCustomers[$email][$websiteCode];
  450. } elseif (isset($this->_newCustomers[$email][$websiteCode])) {
  451. return $this->_newCustomers[$email][$websiteCode];
  452. } else {
  453. return null;
  454. }
  455. }
  456. /**
  457. * EAV entity type code getter.
  458. *
  459. * @abstract
  460. * @return string
  461. */
  462. public function getEntityTypeCode()
  463. {
  464. return 'customer';
  465. }
  466. /**
  467. * Obtain scope of the row from row data.
  468. *
  469. * @param array $rowData
  470. * @return int
  471. */
  472. public function getRowScope(array $rowData)
  473. {
  474. return strlen(trim($rowData[self::COL_EMAIL])) ? self::SCOPE_DEFAULT : self::SCOPE_ADDRESS;
  475. }
  476. /**
  477. * Is attribute contains particular data (not plain entity attribute).
  478. *
  479. * @param string $attrCode
  480. * @return bool
  481. */
  482. public function isAttributeParticular($attrCode)
  483. {
  484. return parent::isAttributeParticular($attrCode) || $this->_addressEntity->isAttributeParticular($attrCode);
  485. }
  486. /**
  487. * Validate data row.
  488. *
  489. * @param array $rowData
  490. * @param int $rowNum
  491. * @return boolean
  492. */
  493. public function validateRow(array $rowData, $rowNum)
  494. {
  495. static $email = null; // e-mail is remembered through all customer rows
  496. static $website = null; // website is remembered through all customer rows
  497. if (isset($this->_validatedRows[$rowNum])) { // check that row is already validated
  498. return !isset($this->_invalidRows[$rowNum]);
  499. }
  500. $this->_validatedRows[$rowNum] = true;
  501. $rowScope = $this->getRowScope($rowData);
  502. if (self::SCOPE_DEFAULT == $rowScope) {
  503. $this->_processedEntitiesCount ++;
  504. }
  505. // BEHAVIOR_DELETE use specific validation logic
  506. if (Mage_ImportExport_Model_Import::BEHAVIOR_DELETE == $this->getBehavior()) {
  507. if (self::SCOPE_DEFAULT == $rowScope
  508. && !isset($this->_oldCustomers[$rowData[self::COL_EMAIL]][$rowData[self::COL_WEBSITE]])) {
  509. $this->addRowError(self::ERROR_EMAIL_SITE_NOT_FOUND, $rowNum);
  510. }
  511. } elseif (self::SCOPE_DEFAULT == $rowScope) { // row is SCOPE_DEFAULT = new customer block begins
  512. $email = $rowData[self::COL_EMAIL];
  513. $website = $rowData[self::COL_WEBSITE];
  514. if (!Zend_Validate::is($email, 'EmailAddress')) {
  515. $this->addRowError(self::ERROR_INVALID_EMAIL, $rowNum);
  516. } elseif (!isset($this->_websiteCodeToId[$website])) {
  517. $this->addRowError(self::ERROR_INVALID_WEBSITE, $rowNum);
  518. } else {
  519. if (isset($this->_newCustomers[$email][$website])) {
  520. $this->addRowError(self::ERROR_DUPLICATE_EMAIL_SITE, $rowNum);
  521. }
  522. $this->_newCustomers[$email][$website] = false;
  523. if (!empty($rowData[self::COL_STORE]) && !isset($this->_storeCodeToId[$rowData[self::COL_STORE]])) {
  524. $this->addRowError(self::ERROR_INVALID_STORE, $rowNum);
  525. }
  526. // check password
  527. if (isset($rowData['password']) && strlen($rowData['password'])
  528. && Mage::helper('core/string')->strlen($rowData['password']) < self::MAX_PASSWD_LENGTH
  529. ) {
  530. $this->addRowError(self::ERROR_PASSWORD_LENGTH, $rowNum);
  531. }
  532. // check simple attributes
  533. foreach ($this->_attributes as $attrCode => $attrParams) {
  534. if (in_array($attrCode, $this->_ignoredAttributes)) {
  535. continue;
  536. }
  537. if (isset($rowData[$attrCode]) && strlen($rowData[$attrCode])) {
  538. $this->isAttributeValid($attrCode, $attrParams, $rowData, $rowNum);
  539. } elseif ($attrParams['is_required'] && !isset($this->_oldCustomers[$email][$website])) {
  540. $this->addRowError(self::ERROR_VALUE_IS_REQUIRED, $rowNum, $attrCode);
  541. }
  542. }
  543. }
  544. if (isset($this->_invalidRows[$rowNum])) {
  545. $email = false; // mark row as invalid for next address rows
  546. }
  547. } else {
  548. if (null === $email) { // first row is not SCOPE_DEFAULT
  549. $this->addRowError(self::ERROR_EMAIL_IS_EMPTY, $rowNum);
  550. } elseif (false === $email) { // SCOPE_DEFAULT row is invalid
  551. $this->addRowError(self::ERROR_ROW_IS_ORPHAN, $rowNum);
  552. }
  553. }
  554. // validate row data by address entity
  555. $this->_addressEntity->validateRow($rowData, $rowNum);
  556. return !isset($this->_invalidRows[$rowNum]);
  557. }
  558. }