1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26:
27: 28: 29: 30: 31: 32: 33:
34: abstract class Mage_ImportExport_Model_Import_Entity_Abstract
35: {
36: 37: 38: 39:
40: const DB_MAX_PACKET_COEFFICIENT = 900000;
41: const DB_MAX_PACKET_DATA = 1048576;
42: const DB_MAX_VARCHAR_LENGTH = 256;
43: const DB_MAX_TEXT_LENGTH = 65536;
44:
45: 46: 47: 48: 49:
50: protected $_connection;
51:
52: 53: 54: 55: 56:
57: protected $_dataValidated = false;
58:
59: 60: 61: 62: 63:
64: protected $_dataSourceModel;
65:
66: 67: 68: 69: 70:
71: protected $_entityTypeId;
72:
73: 74: 75: 76: 77:
78: protected $_errors = array();
79:
80: 81: 82: 83: 84:
85: protected $_errorsCount = 0;
86:
87: 88: 89: 90: 91:
92: protected $_errorsLimit = 100;
93:
94: 95: 96: 97: 98:
99: protected $_importAllowed = true;
100:
101: 102: 103: 104: 105:
106: protected $_indexValueAttributes = array();
107:
108: 109: 110: 111: 112:
113: protected $_invalidRows = array();
114:
115: 116: 117: 118: 119:
120: protected $_messageTemplates = array();
121:
122: 123: 124: 125: 126:
127: protected $_notices = array();
128:
129: 130: 131: 132: 133:
134: protected $_parameters = array();
135:
136: 137: 138: 139: 140:
141: protected $_particularAttributes = array();
142:
143: 144: 145: 146: 147:
148: protected $_permanentAttributes = array();
149:
150: 151: 152: 153: 154:
155: protected $_processedEntitiesCount = 0;
156:
157: 158: 159: 160: 161:
162: protected $_processedRowsCount = 0;
163:
164: 165: 166: 167: 168: 169: 170: 171: 172:
173: protected $_rowsToSkip = array();
174:
175: 176: 177: 178: 179:
180: protected $_validatedRows = array();
181:
182: 183: 184: 185: 186:
187: protected $_source;
188:
189: 190: 191: 192: 193:
194: protected $_uniqueAttributes = array();
195:
196: 197: 198: 199: 200:
201: public function __construct()
202: {
203: $entityType = Mage::getSingleton('eav/config')->getEntityType($this->getEntityTypeCode());
204: $this->_entityTypeId = $entityType->getEntityTypeId();
205: $this->_dataSourceModel = Mage_ImportExport_Model_Import::getDataSourceModel();
206: $this->_connection = Mage::getSingleton('core/resource')->getConnection('write');
207: }
208:
209: 210: 211: 212: 213:
214: protected function _getSource()
215: {
216: if (!$this->_source) {
217: Mage::throwException(Mage::helper('importexport')->__('No source specified'));
218: }
219: return $this->_source;
220: }
221:
222: 223: 224: 225: 226: 227:
228: abstract protected function _importData();
229:
230: 231: 232: 233: 234: 235:
236: protected function _isRowScopeDefault(array $rowData)
237: {
238: return true;
239: }
240:
241: 242: 243: 244: 245: 246:
247: protected function _prepareRowForDb(array $rowData)
248: {
249: 250: 251: 252: 253:
254: foreach ($rowData as $key => $val) {
255: if ($val === '') {
256: $rowData[$key] = null;
257: }
258: }
259: return $rowData;
260: }
261:
262: 263: 264: 265: 266:
267: protected function _saveValidatedBunches()
268: {
269: $source = $this->_getSource();
270: $productDataSize = 0;
271: $bunchRows = array();
272: $startNewBunch = false;
273: $nextRowBackup = array();
274: $maxDataSize = Mage::getResourceHelper('importexport')->getMaxDataSize();
275: $bunchSize = Mage::helper('importexport')->getBunchSize();
276:
277: $source->rewind();
278: $this->_dataSourceModel->cleanBunches();
279:
280: while ($source->valid() || $bunchRows) {
281: if ($startNewBunch || !$source->valid()) {
282: $this->_dataSourceModel->saveBunch($this->getEntityTypeCode(), $this->getBehavior(), $bunchRows);
283:
284: $bunchRows = $nextRowBackup;
285: $productDataSize = strlen(serialize($bunchRows));
286: $startNewBunch = false;
287: $nextRowBackup = array();
288: }
289: if ($source->valid()) {
290: if ($this->_errorsCount >= $this->_errorsLimit) {
291: return;
292: }
293: $rowData = $source->current();
294:
295: $this->_processedRowsCount++;
296:
297: if ($this->validateRow($rowData, $source->key())) {
298: $rowData = $this->_prepareRowForDb($rowData);
299: $rowSize = strlen(Mage::helper('core')->jsonEncode($rowData));
300:
301: $isBunchSizeExceeded = ($bunchSize > 0 && count($bunchRows) >= $bunchSize);
302:
303: if (($productDataSize + $rowSize) >= $maxDataSize || $isBunchSizeExceeded) {
304: $startNewBunch = true;
305: $nextRowBackup = array($source->key() => $rowData);
306: } else {
307: $bunchRows[$source->key()] = $rowData;
308: $productDataSize += $rowSize;
309: }
310: }
311: $source->next();
312: }
313: }
314: return $this;
315: }
316:
317: 318: 319: 320: 321: 322: 323: 324:
325: public function addRowError($errorCode, $errorRowNum, $colName = null)
326: {
327: $this->_errors[$errorCode][] = array($errorRowNum + 1, $colName);
328: $this->_invalidRows[$errorRowNum] = true;
329: $this->_errorsCount ++;
330:
331: return $this;
332: }
333:
334: 335: 336: 337: 338: 339: 340:
341: public function addMessageTemplate($errorCode, $message)
342: {
343: $this->_messageTemplates[$errorCode] = $message;
344:
345: return $this;
346: }
347:
348: 349: 350: 351: 352: 353: 354:
355: public function getAttributeOptions(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $indexValAttrs = array())
356: {
357: $options = array();
358:
359: if ($attribute->usesSource()) {
360:
361: $indexValAttrs = array_merge($indexValAttrs, $this->_indexValueAttributes);
362:
363:
364: $index = in_array($attribute->getAttributeCode(), $indexValAttrs) ? 'value' : 'label';
365:
366:
367: $attribute->setStoreId(Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID);
368:
369: try {
370: foreach ($attribute->getSource()->getAllOptions(false) as $option) {
371: $value = is_array($option['value']) ? $option['value'] : array($option);
372: foreach ($value as $innerOption) {
373: if (strlen($innerOption['value'])) {
374: $options[strtolower($innerOption[$index])] = $innerOption['value'];
375: }
376: }
377: }
378: } catch (Exception $e) {
379:
380: }
381: }
382: return $options;
383: }
384:
385: 386: 387: 388: 389:
390: public function getBehavior()
391: {
392: if (!isset($this->_parameters['behavior'])
393: || ($this->_parameters['behavior'] != Mage_ImportExport_Model_Import::BEHAVIOR_APPEND
394: && $this->_parameters['behavior'] != Mage_ImportExport_Model_Import::BEHAVIOR_REPLACE
395: && $this->_parameters['behavior'] != Mage_ImportExport_Model_Import::BEHAVIOR_DELETE)) {
396: return Mage_ImportExport_Model_Import::getDefaultBehavior();
397: }
398: return $this->_parameters['behavior'];
399: }
400:
401: 402: 403: 404: 405: 406:
407: abstract public function getEntityTypeCode();
408:
409: 410: 411: 412: 413:
414: public function getEntityTypeId()
415: {
416: return $this->_entityTypeId;
417: }
418:
419: 420: 421: 422: 423:
424: public function getErrorMessages()
425: {
426: $translator = Mage::helper('importexport');
427: $messages = array();
428:
429: foreach ($this->_errors as $errorCode => $errorRows) {
430: if (isset($this->_messageTemplates[$errorCode])) {
431: $errorCode = $translator->__($this->_messageTemplates[$errorCode]);
432: }
433: foreach ($errorRows as $errorRowData) {
434: $key = $errorRowData[1] ? sprintf($errorCode, $errorRowData[1]) : $errorCode;
435: $messages[$key][] = $errorRowData[0];
436: }
437: }
438: return $messages;
439: }
440:
441: 442: 443: 444: 445:
446: public function getErrorsCount()
447: {
448: return $this->_errorsCount;
449: }
450:
451: 452: 453: 454: 455:
456: public function getErrorsLimit()
457: {
458: return $this->_errorsLimit;
459: }
460:
461: 462: 463: 464: 465:
466: public function getInvalidRowsCount()
467: {
468: return count($this->_invalidRows);
469: }
470:
471: 472: 473: 474: 475:
476: public function getNotices()
477: {
478: return $this->_notices;
479: }
480:
481: 482: 483: 484: 485:
486: public function getProcessedEntitiesCount()
487: {
488: return $this->_processedEntitiesCount;
489: }
490:
491: 492: 493: 494: 495:
496: public function getProcessedRowsCount()
497: {
498: return $this->_processedRowsCount;
499: }
500:
501: 502: 503: 504: 505: 506:
507: public function getSource()
508: {
509: if (!$this->_source) {
510: Mage::throwException(Mage::helper('importexport')->__('Source is not set'));
511: }
512: return $this->_source;
513: }
514:
515: 516: 517: 518: 519:
520: public function importData()
521: {
522: return $this->_importData();
523: }
524:
525: 526: 527: 528: 529: 530:
531: public function isAttributeParticular($attrCode)
532: {
533: return in_array($attrCode, $this->_particularAttributes);
534: }
535:
536: 537: 538: 539: 540: 541: 542: 543: 544:
545: public function isAttributeValid($attrCode, array $attrParams, array $rowData, $rowNum)
546: {
547: switch ($attrParams['type']) {
548: case 'varchar':
549: $val = Mage::helper('core/string')->cleanString($rowData[$attrCode]);
550: $valid = Mage::helper('core/string')->strlen($val) < self::DB_MAX_VARCHAR_LENGTH;
551: break;
552: case 'decimal':
553: $val = trim($rowData[$attrCode]);
554: $valid = (float)$val == $val;
555: break;
556: case 'select':
557: case 'multiselect':
558: $valid = isset($attrParams['options'][strtolower($rowData[$attrCode])]);
559: break;
560: case 'int':
561: $val = trim($rowData[$attrCode]);
562: $valid = (int)$val == $val;
563: break;
564: case 'datetime':
565: $val = trim($rowData[$attrCode]);
566: $valid = strtotime($val) !== false
567: || preg_match('/^\d{2}.\d{2}.\d{2,4}(?:\s+\d{1,2}.\d{1,2}(?:.\d{1,2})?)?$/', $val);
568: break;
569: case 'text':
570: $val = Mage::helper('core/string')->cleanString($rowData[$attrCode]);
571: $valid = Mage::helper('core/string')->strlen($val) < self::DB_MAX_TEXT_LENGTH;
572: break;
573: default:
574: $valid = true;
575: break;
576: }
577:
578: if (!$valid) {
579: $this->addRowError(Mage::helper('importexport')->__("Invalid value for '%s'"), $rowNum, $attrCode);
580: } elseif (!empty($attrParams['is_unique'])) {
581: if (isset($this->_uniqueAttributes[$attrCode][$rowData[$attrCode]])) {
582: $this->addRowError(Mage::helper('importexport')->__("Duplicate Unique Attribute for '%s'"), $rowNum, $attrCode);
583: return false;
584: }
585: $this->_uniqueAttributes[$attrCode][$rowData[$attrCode]] = true;
586: }
587: return (bool) $valid;
588: }
589:
590: 591: 592: 593: 594:
595: public function isDataValid()
596: {
597: $this->validateData();
598: return 0 == $this->_errorsCount;
599: }
600:
601: 602: 603: 604: 605:
606: public function isImportAllowed()
607: {
608: return $this->_importAllowed;
609: }
610:
611: 612: 613: 614: 615: 616: 617:
618: public function isRowAllowedToImport(array $rowData, $rowNum)
619: {
620: return $this->validateRow($rowData, $rowNum) && !isset($this->_rowsToSkip[$rowNum]);
621: }
622:
623: 624: 625: 626: 627: 628: 629:
630: abstract public function validateRow(array $rowData, $rowNum);
631:
632: 633: 634: 635: 636: 637:
638: public function setParameters(array $params)
639: {
640: $this->_parameters = $params;
641: return $this;
642: }
643:
644: 645: 646: 647: 648: 649:
650: public function setSource(Mage_ImportExport_Model_Import_Adapter_Abstract $source)
651: {
652: $this->_source = $source;
653: $this->_dataValidated = false;
654:
655: return $this;
656: }
657:
658: 659: 660: 661: 662: 663:
664: public function validateData()
665: {
666: if (!$this->_dataValidated) {
667:
668: if (($colsAbsent = array_diff($this->_permanentAttributes, $this->_getSource()->getColNames()))) {
669: Mage::throwException(
670: Mage::helper('importexport')->__('Can not find required columns: %s', implode(', ', $colsAbsent))
671: );
672: }
673:
674:
675: $this->_errors = array();
676: $this->_invalidRows = array();
677:
678:
679: $invalidColumns = array();
680:
681: foreach ($this->_getSource()->getColNames() as $colName) {
682: if (!preg_match('/^[a-z][a-z0-9_]*$/', $colName) && !$this->isAttributeParticular($colName)) {
683: $invalidColumns[] = $colName;
684: }
685: }
686: if ($invalidColumns) {
687: Mage::throwException(
688: Mage::helper('importexport')->__('Column names: "%s" are invalid', implode('", "', $invalidColumns))
689: );
690: }
691: $this->_saveValidatedBunches();
692:
693: $this->_dataValidated = true;
694: }
695: return $this;
696: }
697: }
698: