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: 35:
36:
37:
38:
39: 40: 41: 42: 43: 44: 45: 46:
47: class ImportExportBehavior extends CBehavior {
48:
49: private $importRelations = array();
50: private $createdLinkedModels = array();
51: private $modelContainer = array(
52: 'Tags' => array(),
53: 'ActionText' => array(),
54: );
55:
56: 57: 58: 59: 60:
61: public function sendFile($file, $deleteAfterSend=false){
62: if(!preg_match('/(\.\.|\/)/', $file)){
63: $file = Yii::app()->file->set($this->safePath($file));
64: return $file->send(false, false, $deleteAfterSend);
65: }
66: return false;
67: }
68:
69: 70: 71: 72: 73:
74: public function safePath($filename = 'data.csv'){
75: return implode(DIRECTORY_SEPARATOR, array(
76: Yii::app()->basePath,
77: 'data',
78: $filename
79: ));
80: }
81:
82: 83: 84: 85:
86: public function getImportDelimeter() {
87: if (array_key_exists ('importDelimeter', $_SESSION) &&
88: strlen ($_SESSION['importDelimeter']) === 1)
89: return $_SESSION['importDelimeter'];
90: else
91: return ',';
92: }
93:
94: 95: 96: 97:
98: public function getImportEnclosure() {
99: if (array_key_exists ('importEnclosure', $_SESSION) &&
100: strlen ($_SESSION['importEnclosure']) === 1)
101: return $_SESSION['importEnclosure'];
102: else
103: return '"';
104: }
105:
106: 107: 108: 109: 110: 111:
112: protected function importTags($modelName, $tagsField) {
113: $tagAttributes = array();
114: if (empty($tagsField)) return array();
115:
116:
117: $tags = explode(',', $tagsField);
118: foreach ($tags as $tagName) {
119: if (empty($tagName)) continue;
120: $tag = new Tags;
121: $tag->tag = $tagName;
122: $tag->type = $modelName;
123: $tag->timestamp = time();
124: $tag->taggedBy = Yii::app()->getSuName();
125: $tagAttributes[] = $tag->attributes;
126: }
127: $this->modelContainer['Tags'][] = $tagAttributes;
128: }
129:
130: 131: 132: 133:
134: private function getNextImportId() {
135: $criteria = new CDbCriteria;
136: $criteria->order = "importId DESC";
137: $criteria->limit = 1;
138: $import = Imports::model()->find($criteria);
139:
140:
141:
142: if (isset($import)) {
143: $importId = $import->importId + 1;
144: } else {
145: $importId = 1;
146: }
147: return $importId;
148: }
149:
150: 151: 152: 153: 154: 155: 156:
157: protected function availableImportMaps($model = null) {
158: $maps = array();
159: if ($model === "X2Leads")
160: $model = "Leads";
161: $modelName = (isset($model)) ? lcfirst($model) : '.*';
162: $importMapDir = "importMaps";
163: $files = scandir($this->safePath($importMapDir));
164: foreach ($files as $file) {
165: $filename = basename($file);
166:
167: if (!preg_match('/^.*-' . $modelName . '\.json$/', $filename))
168: continue;
169: $mapping = file_get_contents($this->safePath($importMapDir . DIRECTORY_SEPARATOR . $file));
170: $mapping = json_decode($mapping, true);
171: $maps[$file] = $mapping['name'];
172: }
173: return $maps;
174: }
175:
176: 177: 178: 179: 180:
181: protected function loadImportMap($filename) {
182: if (empty($filename))
183: return null;
184: $importMapDir = "importMaps";
185: $map = file_get_contents($this->safePath($importMapDir . DIRECTORY_SEPARATOR . $filename));
186: $map = json_decode($map, true);
187: return $map;
188: }
189:
190: 191: 192: 193: 194: 195: 196: 197: 198:
199: protected function verifyImportMap($model, $keys, $attributes, $createFields = false) {
200: if (!empty($keys) && !empty($attributes)) {
201:
202: $importMap = array_combine($keys, $attributes);
203: $conflictingFields = array();
204: $failedFields = array();
205:
206:
207: $mappedValues = array();
208: $multiMappings = array();
209:
210: foreach ($importMap as $key => &$value) {
211: if (in_array($value, $mappedValues) && !empty($value) && !in_array($value, $multiMappings)) {
212:
213: $multiMappings[] = $value;
214: } else if ($value !== 'createNew') {
215: $mappedValues[] = $value;
216: }
217:
218: $origKey = $key;
219: $key = Formatter::deCamelCase($key);
220: $key = preg_replace('/\[W|_]/', ' ', $key);
221: $key = mb_convert_case($key, MB_CASE_TITLE, "UTF-8");
222: $key = preg_replace('/\W/', '', $key);
223: if ($value == 'createNew' && !$createFields) {
224: $importMap[$origKey] = 'c_' . strtolower($key);
225: $fieldLookup = Fields::model()->findByAttributes(array(
226: 'modelName' => $model,
227: 'fieldName' => $key
228: ));
229: if (isset($fieldLookup)) {
230: $conflictingFields[] = $key;
231: continue;
232: } else {
233: $customFieldLookup = Fields::model()->findByAttributes(array(
234: 'modelName' => $model,
235: 'fieldName' => $importMap[$origKey]
236: ));
237: if (!$customFieldLookup instanceof Fields) {
238:
239: $columnName = strtolower($key);
240: $field = new Fields;
241: $field->modelName = $model;
242: $field->type = "varchar";
243: $field->fieldName = $columnName;
244: $field->required = 0;
245: $field->searchable = 1;
246: $field->relevance = "Medium";
247: $field->custom = 1;
248: $field->modified = 1;
249: $field->attributeLabel = $field->generateAttributeLabel($key);
250: if (!$field->save()) {
251: $failedFields[] = $key;
252: }
253: }
254: }
255: }
256: }
257:
258:
259: $requiredAttrs = Yii::app()->db->createCommand()
260: ->select('fieldName, attributeLabel')
261: ->from('x2_fields')
262: ->where('modelName = :model AND required = 1', array(
263: ':model' => str_replace(' ', '', $model)))
264: ->query();
265: $missingAttrs = array();
266: foreach ($requiredAttrs as $attr) {
267:
268: if (strtolower($attr['fieldName']) == 'visibility')
269: continue;
270:
271: if ($model === 'Contacts' && ($attr['fieldName'] === 'firstName' || $attr['fieldName'] === 'lastName') && in_array('name', array_values($importMap)))
272: continue;
273:
274: if (!in_array($attr['fieldName'], array_values($importMap)))
275: $missingAttrs[] = $attr['attributeLabel'];
276: }
277: if (!empty($conflictingFields)) {
278: $result = array("2", implode(', ', $conflictingFields));
279: } else if (!empty($missingAttrs)) {
280: $result = array("3", implode(', ', $missingAttrs));
281: } else if (!empty($failedFields)) {
282: $result = array("1", implode(', ', $failedFields));
283: } else if (!empty($multiMappings)) {
284: $result = array("4", implode(', ', $multiMappings));
285: } else {
286: $result = array("0");
287: }
288: $_SESSION['importMap'] = $importMap;
289: } else {
290: $result = array("0");
291: $_SESSION['importMap'] = array();
292: }
293: return $result;
294: }
295:
296: 297: 298: 299: 300: 301: 302:
303: protected function normalizeImportMap($map, $fields) {
304: foreach ($fields as $field) {
305: if (!array_key_exists ($field, $map)) {
306: $map[$field] = null;
307: }
308: }
309: return $map;
310: }
311:
312: 313: 314: 315:
316: protected function calculateCsvLength($csvfile) {
317: $lineCount = null;
318: ini_set('auto_detect_line_endings', 1);
319: $fp = fopen ($csvfile, 'r');
320: if ($fp) {
321: $lineCount = 0;
322: while (true) {
323: $arr = fgetcsv ($fp);
324: if ($arr !== false && !is_null ($arr)) {
325: if ($arr === array (null)) {
326: continue;
327: } else {
328: $lineCount++;
329: }
330: } else {
331: break;
332: }
333: }
334: }
335: return $lineCount - 1;
336: }
337:
338: 339: 340: 341:
342: protected function fixCsvLineEndings($csvFile) {
343: $text = file_get_contents($csvFile);
344: $replacement = preg_replace('/\r([^\n])/m', "\r\n\\1", $text);
345: file_put_contents($csvFile, $replacement);
346: }
347:
348: 349: 350: 351: 352:
353: protected function initializeModelImporter($fp) {
354: $meta = fgetcsv($fp);
355: if ($meta === false)
356: throw new Exception('There was an error parsing the models of the CSV.');
357: while ("" === end($meta)) {
358: array_pop($meta);
359: }
360: if (count($meta) == 1) {
361: $version = $meta[0];
362: $meta = fgetcsv($fp);
363: if ($meta === false)
364: throw new Exception('There was an error parsing the contents of the CSV.');
365: while ("" === end($meta)) {
366: array_pop($meta);
367: }
368: }
369: if (empty($meta)) {
370: $_SESSION['errors'] = Yii::t('admin', "Empty CSV or no metadata specified");
371: $this->redirect('importModels');
372: }
373:
374:
375: $failedContacts = fopen($this->safePath('failedRecords.csv'), 'w+');
376: $failedHeader = $meta;
377: if (end($meta) != 'X2_Import_Failures')
378: $failedHeader[] = 'X2_Import_Failures';
379: else
380: array_pop ($meta);
381: fputcsv($failedContacts, $failedHeader);
382: fclose($failedContacts);
383:
384:
385: $_SESSION['offset'] = ftell($fp);
386: $_SESSION['metaData'] = $meta;
387:
388:
389: if (array_key_exists('model', $_SESSION))
390: $modelName = str_replace(' ', '', $_SESSION['model']);
391: else
392: $this->errorMessage(Yii::t('admin', "Session information has been lost. Please retry your import."
393: ));
394: $x2attributes = array_keys(X2Model::model($modelName)->attributes);
395: while ("" === end($x2attributes)) {
396: array_pop($x2attributes);
397: }
398: if ($modelName === 'Actions') {
399:
400: $x2attributes[] = 'actionDescription';
401: }
402:
403: $_SESSION['importMap'] = array();
404: $_SESSION['imported'] = 0;
405: $_SESSION['failed'] = 0;
406: $_SESSION['created'] = 0;
407: $_SESSION['fields'] = X2Model::model($modelName)->getFields(true);
408: $_SESSION['x2attributes'] = $x2attributes;
409: $_SESSION['mapName'] = "";
410: $_SESSION['importId'] = $this->getNextImportId();
411:
412: return array($meta, $x2attributes);
413: }
414:
415: 416: 417: 418: 419: 420: 421:
422: protected function createImportMap($attributes, $meta) {
423:
424: $originalAttributes = $attributes;
425:
426: $attributes = array_map('strtolower', $attributes);
427: $processedMeta = array_map('strtolower', $meta);
428:
429: $processedMeta = preg_replace('/[\W|_]/', '', $processedMeta);
430:
431: $labels = X2Model::model(str_replace(' ', '', $_SESSION['model']))->attributeLabels();
432: $labels = array_map('strtolower', $labels);
433: $labels = preg_replace('/[\W|_]/', '', $labels);
434: 435: 436: 437: 438: 439: 440:
441: foreach ($meta as $metaVal) {
442:
443: if ($metaVal == 'X2_Import_Failures')
444: continue;
445: if ($metaVal === 'tags') {
446: $_SESSION['importMap']['applyTags'] = $metaVal;
447: continue;
448: }
449:
450: $originalMetaVal = $metaVal;
451: $metaVal = strtolower(preg_replace('/[\W|_]/', '', $metaVal));
452: 453: 454: 455: 456: 457: 458:
459: if (in_array($metaVal, $attributes)) {
460: $attrKey = array_search($metaVal, $attributes);
461: $_SESSION['importMap'][$originalAttributes[$attrKey]] = $originalMetaVal;
462: 463: 464: 465: 466: 467:
468: } elseif (in_array($metaVal, $labels)) {
469: $attrKey = array_search($metaVal, $labels);
470: $_SESSION['importMap'][$attrKey] = $originalMetaVal;
471: 472: 473: 474: 475: 476: 477:
478: } elseif (count(preg_grep("/\b$metaVal/i", $attributes)) > 0) {
479: $keys = array_keys(preg_grep("/\b$metaVal/i", $attributes));
480: $attrKey = $keys[0];
481: if (!isset($_SESSION['importMap'][$originalMetaVal]))
482: $_SESSION['importMap'][$originalAttributes[$attrKey]] = $originalMetaVal;
483: 484: 485: 486:
487: }elseif (count(preg_grep("/\b$metaVal/i", $labels)) > 0) {
488: $keys = array_keys(preg_grep("/\b$metaVal/i", $labels));
489: $attrKey = $keys[0];
490: if (!isset($_SESSION['importMap'][$originalMetaVal]))
491: $_SESSION['importMap'][$attrKey] = $originalMetaVal;
492: }
493: }
494: 495: 496: 497: 498: 499: 500: 501: 502:
503: foreach ($originalAttributes as $attribute) {
504: if (in_array($attribute, $processedMeta)) {
505: $metaKey = array_search($attribute, $processedMeta);
506: $_SESSION['importMap'][$attribute] = $meta[$metaKey];
507: } elseif (count(preg_grep("/\b$attribute/i", $processedMeta)) > 0) {
508: $matches = preg_grep("/\b$attribute/i", $processedMeta);
509: $metaKeys = array_keys($matches);
510: $metaValue = $meta[$metaKeys[0]];
511: if (!isset($_SESSION['importMap'][$attribute]))
512: $_SESSION['importMap'][$attribute] = $metaValue;
513: }
514: }
515: }
516:
517: 518: 519: 520:
521: protected function setCurrentActionText($attributes = null) {
522: if (is_null($attributes))
523: $this->modelContainer['ActionText'][] = array();
524: else {
525: $containerId = count($this->modelContainer['ActionText']) - 1;
526: $this->modelContainer['ActionText'][$containerId] = $attributes;
527: }
528: }
529:
530: 531: 532: 533: 534: 535: 536: 537: 538:
539: protected function importRecordAttribute($modelName, X2Model $model, $fieldName, $importAttribute) {
540:
541: $fieldRecord = Fields::model()->findByAttributes(array(
542: 'modelName' => $modelName,
543: 'fieldName' => $fieldName,
544: ));
545:
546:
547:
548: if (empty($importAttribute) && ($importAttribute !== 0 && $importAttribute !== '0')) {
549: return $model;
550: }
551: if ($fieldName === 'actionDescription' && $modelName === 'Actions') {
552: $text = new ActionText;
553: $text->text = $importAttribute;
554: if (isset($model->id))
555: $text->actionId = $model->id;
556: $this->setCurrentActionText ($text->attributes);
557: return $model;
558: }
559:
560:
561: if ((strtolower($fieldName) === 'id') && (!preg_match('/^\d+$/', $importAttribute) || $importAttribute >= 4294967295)) {
562: $model->id = $importAttribute;
563: $model->addError ('id', Yii::t('importexport', "ID '$importAttribute' is not valid."));
564: return $model;
565: }
566:
567: switch ($fieldRecord->type) {
568: case "link":
569: $model = $this->importRecordLinkAttribute($modelName, $model, $fieldRecord, $importAttribute);
570: break;
571: case "dateTime":
572: case "date":
573: if (Formatter::parseDateTime ($importAttribute) !== false)
574: $model->$fieldName = Formatter::parseDateTime ($importAttribute);
575: break;
576: case "visibility":
577: switch ($importAttribute) {
578: case 'Private':
579: $model->$fieldName = 0;
580: break;
581: case 'Public':
582: $model->$fieldName = 1;
583: break;
584: case 'User\'s Groups':
585: $model->$fieldName = 2;
586: break;
587: default:
588: $model->$fieldName = $importAttribute;
589: }
590: break;
591: default:
592: $model->$fieldName = $importAttribute;
593: }
594: return $model;
595: }
596:
597: 598: 599: 600: 601: 602: 603: 604:
605: protected function importRecordLinkAttribute($modelName, X2Model $model, Fields $fieldRecord, $importAttribute) {
606: $fieldName = $fieldRecord->fieldName;
607: $className = ucfirst($fieldRecord->linkType);
608: if (isset($_SESSION['linkMatchMap']) && !empty($_SESSION['linkMatchMap'][$fieldName])) {
609: $linkMatchAttribute = $_SESSION['linkMatchMap'][$fieldName];
610: }
611:
612: if (ctype_digit($importAttribute) && !isset($linkMatchAttribute)) {
613: $lookup = X2Model::model($className)->findByPk($importAttribute);
614: $model->$fieldName = $importAttribute;
615: if (!empty($lookup)) {
616:
617: $model->$fieldName = $lookup->nameId;
618: $relationship = new Relationships;
619: $relationship->firstType = $modelName;
620: $relationship->secondType = $className;
621: $relationship->secondId = $importAttribute;
622: $this->importRelations[count($this->importRelations) - 1][] = $relationship->attributes;
623: }
624: } else {
625: $lookupAttr = isset($linkMatchAttribute) ? $linkMatchAttribute : 'name';
626: $lookup = X2Model::model($className)->findByAttributes(array($lookupAttr => $importAttribute));
627: if (isset($lookup)) {
628: $model->$fieldName = $lookup->nameId;
629: $relationship = new Relationships;
630: $relationship->firstType = $modelName;
631: $relationship->secondType = $className;
632: $relationship->secondId = $lookup->id;
633: $this->importRelations[count($this->importRelations) - 1][] = $relationship->attributes;
634: } elseif (isset($_SESSION['createRecords']) && $_SESSION['createRecords'] == 1 &&
635: !($modelName === 'BugReports' && $fieldRecord->linkType === 'BugReports')) {
636:
637: $className = ucfirst($fieldRecord->linkType);
638: if (class_exists($className)) {
639: $lookup = new $className;
640: if ($_SESSION['skipActivityFeed'] === 1)
641: $lookup->createEvent = false;
642: $lookup->name = $importAttribute;
643: if ($className === 'Contacts' || $className === 'X2Leads') {
644: self::fixupImportedContactName($lookup);
645: }
646: if ($lookup->hasAttribute('visibility'))
647: $lookup->visibility = 1;
648: if ($lookup->hasAttribute('description'))
649: $lookup->description = "Generated by " . $modelName . " import.";
650: if ($lookup->hasAttribute('createDate'))
651: $lookup->createDate = time();
652:
653: if (!array_key_exists($className, $this->modelContainer))
654: $this->modelContainer[$className] = array();
655:
656:
657: $createNewLinkedRecord = true;
658: if ($model->hasAttribute('name')) {
659: $model->$fieldName = $lookup->name;
660: } else {
661: $model->$fieldName = $importAttribute;
662: }
663:
664: foreach ($this->modelContainer[$className] as $record) {
665: if ($record['name'] === $lookup->name) {
666: $createNewLinkedRecord = false;
667: break;
668: }
669: }
670:
671: if ($createNewLinkedRecord) {
672: $this->modelContainer[$className][] = $lookup->attributes;
673: if (isset($_SESSION['created'][$className])) {
674: $_SESSION['created'][$className] ++;
675: } else {
676: $_SESSION['created'][$className] = 1;
677: }
678: }
679: $relationship = new Relationships;
680: $relationship->firstType = $modelName;
681: $relationship->secondType = $className;
682: $this->importRelations[count($this->importRelations) - 1][] = $relationship->attributes;
683: $this->createdLinkedModels[] = $model->$fieldName;
684: }
685: } else {
686: $model->$fieldName = $importAttribute;
687: }
688: }
689: return $model;
690: }
691:
692: 693: 694: 695: 696: 697:
698: protected static function fixupImportedContactName($model) {
699: if (!empty($model->name) || !empty($model->firstName) || !empty($model->lastName)) {
700: $nameFormat = Yii::app()->settings->contactNameFormat;
701: switch ($nameFormat) {
702: case 'lastName, firstName':
703: if (empty ($model->name))
704: $model->name = $model->lastName . ", " . $model->firstName;
705: $decomposePattern = '/^(?P<last>\w+), ?(?P<first>\w+)$/';
706: break;
707: case 'firstName lastName':
708: default:
709: if (empty ($model->name))
710: $model->name = $model->firstName . " " . $model->lastName;
711: $decomposePattern = '/^(?P<first>\w+) (?P<last>\w+)$/';
712: break;
713: }
714: preg_match ($decomposePattern, $model->name, $matches);
715: if (array_key_exists ('first', $matches) && array_key_exists ('last', $matches)) {
716: $model->firstName = $matches['first'];
717: $model->lastName = $matches['last'];
718: }
719: }
720: return $model;
721: }
722:
723: 724: 725: 726: 727: 728: 729:
730: protected function fixupImportedAttributes($modelName, X2Model $model) {
731: if ($modelName === 'Contacts' || $modelName === 'X2Leads')
732: $model = self::fixupImportedContactName($model);
733:
734: if ($modelName === 'Actions' && isset($model->associationType))
735: $model = $this->reconstructImportedActionAssoc($model);
736:
737:
738: if ($model->hasAttribute('visibility') && is_null($model->visibility))
739: $model->visibility = 1;
740:
741: if (!empty($model->createDate) || !empty($model->lastUpdated) ||
742: !empty($model->lastActivity)) {
743: $now = time();
744: if (empty($model->createDate))
745: $model->createDate = $now;
746: if (empty($model->lastUpdated))
747: $model->lastUpdated = $now;
748: if ($model->hasAttribute('lastActivity') && empty($model->lastActivity))
749: $model->lastActivity = $now;
750: }
751: if ($_SESSION['leadRouting'] == 1) {
752: $assignee = $this->getNextAssignee();
753: if ($assignee == "Anyone")
754: $assignee = "";
755: $model->assignedTo = $assignee;
756: }
757:
758: foreach ($_SESSION['override'] as $attr => $val) {
759: $model->$attr = $val;
760: }
761:
762: return $model;
763: }
764:
765: 766: 767: 768: 769: 770:
771: protected function saveImportedModel(X2Model $model, $modelName, $importedIds) {
772: if (!empty($model->id)) {
773: $lookup = X2Model::model(str_replace(' ', '', $modelName))->findByPk($model->id);
774: if (isset($lookup)) {
775: Relationships::model()->deleteAllByAttributes(array(
776: 'firstType' => $modelName,
777: 'firstId' => $lookup->id)
778: );
779: Relationships::model()->deleteAllByAttributes(array(
780: 'secondType' => $modelName,
781: 'secondId' => $lookup->id)
782: );
783: $lookup->delete();
784: unset($lookup);
785: }
786: }
787:
788:
789:
790: $this->modelContainer[$modelName][] = $model->attributes;
791: $_SESSION['imported'] ++;
792: $importedIds[] = $model->id;
793: return $importedIds;
794: }
795:
796: 797: 798: 799: 800: 801:
802: protected function insertMultipleRecords($modelType, $models) {
803: if (empty($models))
804: return null;
805: $tableName = X2Model::model($modelType)->tableName();
806: Yii::app()->db->schema->commandBuilder
807: ->createMultipleInsertCommand($tableName, $models)
808: ->execute();
809: $lastInsertId = Yii::app()->db->schema->commandBuilder
810: ->getLastInsertId($tableName);
811: return $lastInsertId;
812: }
813:
814: 815: 816: 817: 818:
819: protected function prepareImportSampleRecords($meta, $fp) {
820: $sampleRecords = array();
821: for ($i = 0; $i < 5; $i++) {
822: if ($sampleRecord = fgetcsv($fp, 0, $_SESSION['delimeter'], $_SESSION['enclosure'])) {
823: if(count($sampleRecord) > count($meta)){
824: $sampleRecord = array_slice($sampleRecord, 0, count($meta));
825: }
826: if (count($sampleRecord) < count($meta)) {
827: $sampleRecord = array_pad($sampleRecord, count($meta), null);
828: }
829: if (!empty($meta)) {
830: $sampleRecord = array_combine($meta, $sampleRecord);
831: $sampleRecords[] = $sampleRecord;
832: }
833: }
834: }
835: return $sampleRecords;
836: }
837:
838: 839: 840: 841: 842:
843: protected function reconstructImportedActionAssoc(Actions $model) {
844: $exportableModules = array_merge(
845: array_keys(Modules::getExportableModules()), array('None')
846: );
847: $exportableModules = array_map('lcfirst', $exportableModules);
848: $model->associationType = lcfirst($model->associationType);
849: if (!in_array($model->associationType, $exportableModules)) {
850:
851: $model->addError('associationType', Yii::t('admin', 'Unknown associationType.'));
852: } else if (isset($model->associationId) && $model->associationId !== '0') {
853: $associatedModel = X2Model::model($model->associationType)
854: ->findByPk($model->associationId);
855: if ($associatedModel)
856: $model->associationName = $associatedModel->nameId;
857: } else if (!isset($model->associationId) && isset($model->associationName)) {
858:
859: $staticAssociationModel = X2Model::model($model->associationType);
860: if ($staticAssociationModel->hasAttribute('name') &&
861: !ctype_digit($model->associationName)) {
862: $associationModelParams = array('name' => $model->associationName);
863: } else {
864: $associationModelParams = array('id' => $model->associationName);
865: }
866: $lookup = $staticAssociationModel->findByAttributes($associationModelParams);
867: if (isset($lookup)) {
868: $model->associationId = $lookup->id;
869: $model->associationName = $lookup->hasAttribute('nameId') ?
870: $lookup->nameId : $lookup->name;
871: }
872: }
873: return $model;
874: }
875:
876: 877: 878: 879: 880: 881: 882: 883:
884: protected function finishImportBatch($modelName, $mappedId, $finished = false) {
885: if (!array_key_exists ($modelName, $this->modelContainer) || empty($this->modelContainer[$modelName])) {
886: $this->importerResponse ($finished);
887: return;
888: }
889:
890:
891: $lastInsertedIds = array();
892:
893:
894: $lastInsertedIds[$modelName] = $this->insertMultipleRecords(
895: $modelName, $this->modelContainer[$modelName]
896: );
897: $primaryModelCount = count($this->modelContainer[$modelName]);
898:
899:
900: if ($mappedId) {
901: $primaryIdRange = range(
902: $lastInsertedIds[$modelName] - $primaryModelCount + 1, $lastInsertedIds[$modelName]
903: );
904: } else {
905: $primaryIdRange = range(
906: $lastInsertedIds[$modelName], $lastInsertedIds[$modelName] + $primaryModelCount - 1
907: );
908: }
909: $this->handleImportAccounting($this->modelContainer[$modelName], $modelName, $lastInsertedIds, $mappedId);
910: $this->massUpdateImportedNameIds($primaryIdRange, $modelName);
911:
912:
913: foreach ($this->modelContainer as $type => $models) {
914: if ($type === $modelName)
915: continue;
916:
917: if ($modelName === 'Actions' && $type === 'ActionText') {
918:
919: $firstInsertedId = $primaryIdRange[0];
920: $actionTexts = array();
921: foreach ($models as $i => $model) {
922: if (empty($model))
923: continue;
924: if (!isset($model['actionId']))
925: $model['actionId'] = $firstInsertedId + $i;
926: $actionTexts[] = $model;
927: }
928: $this->insertMultipleRecords ('ActionText', $actionTexts);
929: } else if ($type === 'Tags') {
930:
931: $firstInsertedId = $primaryIdRange[0];
932: $tags = array();
933: foreach ($models as $i => $tagModels) {
934: if (empty($tagModels))
935: continue;
936: foreach ($tagModels as $tag) {
937: $tag['itemId'] = $firstInsertedId + $i;
938: $tags[] = $tag;
939: }
940: }
941: $this->insertMultipleRecords ('Tags', $tags);
942: } else {
943:
944: $lastInsertedIds[$type] = $this->insertMultipleRecords($type, $models);
945: $this->handleImportAccounting($models, $type, $lastInsertedIds);
946: $this->fixupLinkFields($modelName, $type, $primaryIdRange);
947:
948:
949: $idRange = range(
950: $lastInsertedIds[$type], $lastInsertedIds[$type] + count($models) - 1
951: );
952: $this->massUpdateImportedNameIds ($idRange, $type);
953: $this->triggerImportedRecords ($idRange, $type);
954: }
955: }
956:
957: $this->establishImportRelationships ($primaryIdRange[0], $mappedId);
958: $this->triggerImportedRecords ($primaryIdRange, $modelName);
959: $this->importerResponse ($finished);
960: }
961:
962: 963: 964: 965: 966: 967:
968: protected function massUpdateImportedNameIds($importedIds, $type) {
969: $hasNameId = Fields::model()->findByAttributes(array(
970: 'fieldName' => 'nameId',
971: 'modelName' => $type,
972: ));
973: if ($hasNameId)
974: X2Model::massUpdateNameId($type, $importedIds);
975: }
976:
977: 978: 979: 980: 981:
982: protected function triggerImportedRecords($importedIds, $type) {
983: foreach ($importedIds as $id) {
984: $model = X2Model::model ($type)->findByPk ($id);
985: X2Flow::trigger('RecordCreateTrigger', array('model'=>$model));
986: }
987: }
988:
989: 990: 991:
992: private function importerResponse ($finished) {
993: $finished = !isset ($finished) ? false : $finished;
994: echo json_encode(array(
995: ($finished ? '1' : '0'),
996: $_SESSION['imported'],
997: $_SESSION['failed'],
998: json_encode($_SESSION['created']),
999: ));
1000: }
1001:
1002: 1003: 1004: 1005: 1006: 1007: 1008: 1009:
1010: protected function handleImportAccounting($models, $modelName, $lastInsertedIds, $mappedId = false) {
1011: if (count($models) === 0)
1012: return;
1013: $now = time();
1014: $editingUsername = Yii::app()->user->name;
1015: $auxModelContainer = array(
1016: 'Imports' => array(),
1017: 'Actions' => array(),
1018: 'Events' => array(),
1019: 'Notification' => array(),
1020: 'Tags' => array(),
1021: );
1022: if ($mappedId)
1023: $firstNewId = $lastInsertedIds[$modelName] - count($models) + 1;
1024: else
1025: $firstNewId = $lastInsertedIds[$modelName];
1026:
1027: for ($i = 0; $i < count($models); $i++) {
1028: $record = $models[$i];
1029: if ($mappedId) {
1030: $modelId = $models[$i]['id'];
1031: } else {
1032: $modelId = $i + $firstNewId;
1033: }
1034:
1035:
1036:
1037: if ($_SESSION['skipActivityFeed'] !== 1) {
1038: $event = new Events;
1039: $event->visibility = array_key_exists('visibility', $record) ?
1040: $record['visibility'] : 1;
1041: $event->associationType = $modelName;
1042: $event->associationId = $modelId;
1043: $event->timestamp = $now;
1044: $event->user = $editingUsername;
1045: $event->type = 'record_create';
1046: $auxModelContainer['Events'][] = $event->attributes;
1047: }
1048: if (array_key_exists('assignedTo', $record) && !empty($record['assignedTo']) &&
1049: $record['assignedTo'] != $editingUsername && $record['assignedTo'] != 'Anyone') {
1050: $notif = new Notification;
1051: $notif->user = $record['assignedTo'];
1052: $notif->createdBy = $editingUsername;
1053: $notif->createDate = $now;
1054: $notif->type = 'create';
1055: $notif->modelType = $modelName;
1056: $notif->modelId = $modelId;
1057: $auxModelContainer['Notification'][] = $notif->attributes;
1058: }
1059:
1060:
1061: foreach ($_SESSION['tags'] as $tag) {
1062: $tagModel = new Tags;
1063: $tagModel->taggedBy = 'Import';
1064: $tagModel->timestamp = $now;
1065: $tagModel->type = $modelName;
1066: $tagModel->itemId = $modelId;
1067: $tagModel->tag = $tag;
1068: $tagModel->itemName = $modelName;
1069: $auxModelContainer['Tags'][] = $tagModel->attributes;
1070: }
1071:
1072: if (!empty($_SESSION['comment'])) {
1073: $action = new Actions;
1074: $action->associationType = lcfirst(str_replace(' ', '', $modelName));
1075: $action->associationId = $modelId;
1076: $action->createDate = $now;
1077: $action->updatedBy = Yii::app()->user->getName();
1078: $action->lastUpdated = $now;
1079: $action->complete = "Yes";
1080: $action->completeDate = $now;
1081: $action->completedBy = Yii::app()->user->getName();
1082: $action->type = "note";
1083: $action->visibility = 1;
1084: $action->reminder = "No";
1085: $action->priority = 1;
1086: $auxModelContainer['Actions'][] = $action->attributes;
1087: }
1088:
1089: $importLink = new Imports;
1090: $importLink->modelType = $modelName;
1091: $importLink->modelId = $modelId;
1092: $importLink->importId = $_SESSION['importId'];
1093: $importLink->timestamp = $now;
1094: $auxModelContainer['Imports'][] = $importLink->attributes;
1095: }
1096:
1097: foreach ($auxModelContainer as $type => $records) {
1098: if (empty($records))
1099: continue;
1100: $lastInsertId = $this->insertMultipleRecords($type, $records);
1101: if ($type === 'Actions') {
1102:
1103: if (empty($records))
1104: continue;
1105: $actionImportRecords = array();
1106: $actionTextRecords = array();
1107: $actionIdRange = range($lastInsertId, $lastInsertId + count($records) - 1);
1108: foreach ($actionIdRange as $i) {
1109: $importLink = new Imports;
1110: $importLink->modelType = "Actions";
1111: $importLink->modelId = $i;
1112: $importLink->importId = $_SESSION['importId'];
1113: $importLink->timestamp = $now;
1114: $actionImportRecords[] = $importLink->attributes;
1115:
1116: $actionText = new ActionText;
1117: $actionText->actionId = $i;
1118: $actionText->text = $_SESSION['comment'];
1119: $actionTextRecords[] = $actionText->attributes;
1120: }
1121: $this->insertMultipleRecords('Imports', $actionImportRecords);
1122: $this->insertMultipleRecords('ActionText', $actionTextRecords);
1123: }
1124: }
1125: }
1126:
1127: 1128: 1129: 1130: 1131: 1132: 1133:
1134: protected function fixupLinkFields($modelName, $type, $primaryIdRange) {
1135: $linkTypeFields = Yii::app()->db->createCommand()
1136: ->select('fieldName')
1137: ->from('x2_fields')
1138: ->where('type = "link" AND modelName = :modelName AND linkType = :linkType', array(
1139: ':modelName' => $modelName,
1140: ':linkType' => $type,
1141: ))->queryColumn();
1142: $primaryTable = X2Model::model($modelName)->tableName();
1143: foreach ($linkTypeFields as $field) {
1144:
1145: $staticModel = X2Model::model($type);
1146: if (!$staticModel->hasAttribute('name'))
1147: continue;
1148: $secondaryTable = $staticModel->tableName();
1149:
1150: $sql = 'UPDATE `' . $primaryTable . '` a JOIN `' . $secondaryTable . '` b ' .
1151: 'ON a.' . $field . ' = b.name ' .
1152: 'SET a.`' . $field . '` = CONCAT(b.name, \'' . Fields::NAMEID_DELIM . '\', b.id) ' .
1153: 'WHERE a.id in (' . implode(',', $primaryIdRange) . ')';
1154: Yii::app()->db->createCommand($sql)->execute();
1155: }
1156: }
1157:
1158: 1159: 1160: 1161: 1162:
1163: protected function establishImportRelationships($firstNewId, $mappedId = false) {
1164: $validRelationships = array();
1165:
1166: foreach ($this->importRelations as $i => $modelsRelationships) {
1167: $modelId = $i + $firstNewId;
1168: if (empty($modelsRelationships))
1169: continue;
1170: foreach ($modelsRelationships as $relationship) {
1171: $relationship['firstId'] = $modelId;
1172: if (empty($relationship['secondId'])) {
1173: $model = X2Model::model($relationship['firstType'])
1174: ->findByPk($modelId);
1175: $linkedStaticModel = X2Model::model($relationship['secondType']);
1176: if (!$model)
1177: continue;
1178: $fields = Yii::app()->db->createCommand()
1179: ->select('fieldName')
1180: ->from('x2_fields')
1181: ->where('type = \'link\' AND modelName = :firstType AND ' .
1182: 'linkType = :secondType', array(
1183: ':firstType' => $relationship['firstType'],
1184: ':secondType' => $relationship['secondType'],
1185: ))->queryColumn();
1186: foreach ($fields as $field) {
1187:
1188: if (empty($model->$field))
1189: continue;
1190: $attr = $linkedStaticModel->hasAttribute('nameId') ? 'nameId' : 'name';
1191: $linkedId = Yii::app()->db->createCommand()
1192: ->select('id')
1193: ->from($linkedStaticModel->tableName())
1194: ->where($attr . ' = :reference', array(
1195: ':reference' => $model->$field,
1196: ))->queryScalar();
1197: if (!$linkedId)
1198: continue;
1199: $relationship['secondId'] = $linkedId;
1200: }
1201: }
1202: if (!empty($relationship['secondId']))
1203: $validRelationships[] = $relationship;
1204: }
1205: }
1206: $this->insertMultipleRecords('Relationships', $validRelationships);
1207: }
1208:
1209: 1210: 1211: 1212: 1213: 1214: 1215:
1216: protected function markFailedRecord($modelName, X2Model $model, $csvLine, $metaData) {
1217:
1218: $failedRecords = fopen($this->safePath('failedRecords.csv'), 'a+');
1219: $errorMsg = array();
1220: foreach ($model->errors as $error)
1221: $errorMsg[] = strtr(implode(' ', array_values($error)), '"', "'");
1222: $errorMsg = implode(' ', $errorMsg);
1223:
1224:
1225: if (end($metaData) === 'X2_Import_Failures')
1226: $csvLine[count($csvLine) - 1] = $errorMsg;
1227: else
1228: $csvLine[] = $errorMsg;
1229: fputcsv($failedRecords, $csvLine);
1230: fclose($failedRecords);
1231: $_SESSION['failed']++;
1232:
1233:
1234: if ($modelName === 'Actions')
1235: array_pop ($this->modelContainer['ActionText']);
1236: }
1237:
1238: 1239: 1240:
1241: protected function loadUploadedImportMap() {
1242: $temp = CUploadedFile::getInstanceByName('mapping');
1243: $temp->saveAs($mapPath = $this->safePath('mapping.json'));
1244: $mappingFile = fopen($mapPath, 'r');
1245: $importMap = fread($mappingFile, filesize($mapPath));
1246: $importMap = json_decode($importMap, true);
1247: if ($importMap === null) {
1248: $_SESSION['errors'] = Yii::t('admin', 'Invalid JSON string specified');
1249: $this->redirect('importModels');
1250: }
1251: $_SESSION['importMap'] = $importMap;
1252:
1253: if (array_key_exists('mapping', $importMap)) {
1254: $_SESSION['importMap'] = $importMap['mapping'];
1255: if (isset($importMap['name']))
1256: $_SESSION['mapName'] = $importMap['name'];
1257: else
1258: $_SESSION['mapName'] = Yii::t('admin', "Untitled Mapping");
1259:
1260: $importMap = $importMap['mapping'];
1261: } else {
1262: $_SESSION['importMap'] = $importMap;
1263: $_SESSION['mapName'] = Yii::t('admin', "Untitled Mapping");
1264: }
1265:
1266: fclose($mappingFile);
1267: if (file_exists($mapPath))
1268: unlink($mapPath);
1269: }
1270:
1271: 1272: 1273: 1274: 1275:
1276: protected function readExportFormatOptions($params) {
1277: $paramNames = array(
1278: 'compressOutput',
1279: 'exportDestination',
1280:
1281: );
1282:
1283: $formatParams = array(
1284: 'exportDestination' => 'download',
1285: 'compressOutput' => 'false',
1286: );
1287: foreach ($paramNames as $param) {
1288: if (array_key_exists ($param, $params) && !empty($params[$param]))
1289: $formatParams[$param] = $params[$param];
1290: }
1291: $formatParams['compressOutput'] = $formatParams['compressOutput'] === 'true' ? true : false;
1292: return $formatParams;
1293: }
1294:
1295: 1296: 1297: 1298: 1299: 1300: 1301:
1302: protected function adjustExportPath($path, $params, $filetype = 'csv') {
1303: if (isset($params['compressOutput']) && $params['compressOutput']) {
1304: $path = str_replace('.'.$filetype, '.zip', $path);
1305: if (!preg_match ('/\.zip$/', $path))
1306: $path = $path.'.zip';
1307: } else {
1308: if (!preg_match ('/\.'.$filetype.'$/', $path))
1309: $path = "{$path}.{$filetype}";
1310: }
1311: return $path;
1312: }
1313:
1314: 1315: 1316: 1317: 1318: 1319: 1320:
1321: public function prepareExportDeliverable($src, $params) {
1322: $success = true;
1323: if (!array_key_exists ('mimeType', $params))
1324: $params['mimeType'] = 'text/csv';
1325: if (!array_key_exists ('exportDestination', $params))
1326: return false;
1327:
1328: if (array_key_exists ('compressOutput', $params) && $params['compressOutput']) {
1329:
1330: $zip = Yii::app()->zip;
1331: $dirname = str_replace('.csv', '', $src);
1332: $dst = $dirname .'/'. basename($src);
1333: AppFileUtil::ccopy ($src, $dst);
1334: $zipPath = $this->safePath(basename ($dirname) . '.zip');
1335:
1336: if ($zip->makeZip($dirname, $zipPath)) {
1337: $src = $zipPath;
1338: $params['mimeType'] = 'application/zip';
1339: } else {
1340: $success = false;
1341: }
1342: }
1343:
1344: return $success;
1345: }
1346:
1347:
1348: }
1349:
1350: ?>
1351: