7e71e1cecb144ef5150254da7a3281ddc9b8bae3
[civicrm-core.git] / CRM / Export / BAO / ExportProcessor.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2019
32 */
33
34 /**
35 * Class CRM_Export_BAO_ExportProcessor
36 *
37 * Class to handle logic of export.
38 */
39 class CRM_Export_BAO_ExportProcessor {
40
41 /**
42 * @var int
43 */
44 protected $queryMode;
45
46 /**
47 * @var int
48 */
49 protected $exportMode;
50
51 /**
52 * Array of fields in the main query.
53 *
54 * @var array
55 */
56 protected $queryFields = [];
57
58 /**
59 * Either AND or OR.
60 *
61 * @var string
62 */
63 protected $queryOperator;
64
65 /**
66 * Requested output fields.
67 *
68 * If set to NULL then it is 'primary fields only'
69 * which actually means pretty close to all fields!
70 *
71 * @var array|null
72 */
73 protected $requestedFields;
74
75 /**
76 * Is the contact being merged into a single household.
77 *
78 * @var bool
79 */
80 protected $isMergeSameHousehold;
81
82 /**
83 * Should contacts with the same address be merged.
84 *
85 * @var bool
86 */
87 protected $isMergeSameAddress = FALSE;
88
89 /**
90 * Fields that need to be retrieved for address merge purposes but should not be in output.
91 *
92 * @var array
93 */
94 protected $additionalFieldsForSameAddressMerge = [];
95
96 /**
97 * Get additional non-visible fields for address merge purposes.
98 *
99 * @return array
100 */
101 public function getAdditionalFieldsForSameAddressMerge(): array {
102 return $this->additionalFieldsForSameAddressMerge;
103 }
104
105 /**
106 * Set additional non-visible fields for address merge purposes.
107 */
108 public function setAdditionalFieldsForSameAddressMerge() {
109 if ($this->isMergeSameAddress) {
110 $fields = ['id', 'master_id', 'state_province_id', 'postal_greeting_id', 'addressee_id'];
111 foreach ($fields as $index => $field) {
112 if (!empty($this->getReturnProperties()[$field])) {
113 unset($fields[$index]);
114 }
115 }
116 $this->additionalFieldsForSameAddressMerge = array_fill_keys($fields, 1);
117 }
118 }
119
120 /**
121 * Should contacts with the same address be merged.
122 *
123 * @return bool
124 */
125 public function isMergeSameAddress(): bool {
126 return $this->isMergeSameAddress;
127 }
128
129 /**
130 * Set same address is to be merged.
131 *
132 * @param bool $isMergeSameAddress
133 */
134 public function setIsMergeSameAddress(bool $isMergeSameAddress) {
135 $this->isMergeSameAddress = $isMergeSameAddress;
136 }
137
138 /**
139 * Only export contacts that can receive postal mail.
140 *
141 * Includes being alive, having an address & not having do_not_mail.
142 *
143 * @var bool
144 */
145 protected $isPostalableOnly;
146
147 /**
148 * Key representing the head of household in the relationship array.
149 *
150 * e.g. ['8_b_a' => 'Household Member Is', '8_a_b = 'Household Member Of'.....]
151 *
152 * @var array
153 */
154 protected $relationshipTypes = [];
155
156 /**
157 * Array of properties to retrieve for relationships.
158 *
159 * @var array
160 */
161 protected $relationshipReturnProperties = [];
162
163 /**
164 * IDs of households that have already been exported.
165 *
166 * @var array
167 */
168 protected $exportedHouseholds = [];
169
170 /**
171 * Households to skip during export as they will be exported via their relationships anyway.
172 *
173 * @var array
174 */
175 protected $householdsToSkip = [];
176
177 /**
178 * Additional fields to return.
179 *
180 * This doesn't make much sense when we have a fields set but search build add it's own onto
181 * the 'Primary fields' (all) option.
182 *
183 * @var array
184 */
185 protected $additionalRequestedReturnProperties = [];
186
187 /**
188 * Get additional return properties.
189 *
190 * @return array
191 */
192 public function getAdditionalRequestedReturnProperties() {
193 return $this->additionalRequestedReturnProperties;
194 }
195
196 /**
197 * Set additional return properties.
198 *
199 * @param array $value
200 */
201 public function setAdditionalRequestedReturnProperties($value) {
202 // fix for CRM-7066
203 if (!empty($value['group'])) {
204 unset($value['group']);
205 $value['groups'] = 1;
206 }
207 $this->additionalRequestedReturnProperties = $value;
208 }
209
210 /**
211 * Get return properties by relationship.
212 * @return array
213 */
214 public function getRelationshipReturnProperties() {
215 return $this->relationshipReturnProperties;
216 }
217
218 /**
219 * Export values for related contacts.
220 *
221 * @var array
222 */
223 protected $relatedContactValues = [];
224
225 /**
226 * @var array
227 */
228 protected $returnProperties = [];
229
230 /**
231 * @var array
232 */
233 protected $outputSpecification = [];
234
235 /**
236 * Name of a temporary table created to hold the results.
237 *
238 * Current decision making on when to create a temp table is kinda bad so this might change
239 * a bit as it is reviewed but basically we need a temp table or similar to calculate merging
240 * addresses. Merging households is handled in php. We create a temp table even when we don't need them.
241 *
242 * @var string
243 */
244 protected $temporaryTable;
245
246 /**
247 * @return string
248 */
249 public function getTemporaryTable(): string {
250 return $this->temporaryTable;
251 }
252
253 /**
254 * @param string $temporaryTable
255 */
256 public function setTemporaryTable(string $temporaryTable) {
257 $this->temporaryTable = $temporaryTable;
258 }
259
260 /**
261 * CRM_Export_BAO_ExportProcessor constructor.
262 *
263 * @param int $exportMode
264 * @param array|null $requestedFields
265 * @param string $queryOperator
266 * @param bool $isMergeSameHousehold
267 * @param bool $isPostalableOnly
268 * @param bool $isMergeSameAddress
269 */
270 public function __construct($exportMode, $requestedFields, $queryOperator, $isMergeSameHousehold = FALSE, $isPostalableOnly = FALSE, $isMergeSameAddress = FALSE) {
271 $this->setExportMode($exportMode);
272 $this->setQueryMode();
273 $this->setQueryOperator($queryOperator);
274 $this->setRequestedFields($requestedFields);
275 $this->setRelationshipTypes();
276 $this->setIsMergeSameHousehold($isMergeSameHousehold);
277 $this->setIsPostalableOnly($isPostalableOnly);
278 $this->setIsMergeSameAddress($isMergeSameAddress);
279 $this->setReturnProperties($this->determineReturnProperties());
280 $this->setAdditionalFieldsForSameAddressMerge();
281 }
282
283 /**
284 * @return bool
285 */
286 public function isPostalableOnly() {
287 return $this->isPostalableOnly;
288 }
289
290 /**
291 * @param bool $isPostalableOnly
292 */
293 public function setIsPostalableOnly($isPostalableOnly) {
294 $this->isPostalableOnly = $isPostalableOnly;
295 }
296
297 /**
298 * @return array|null
299 */
300 public function getRequestedFields() {
301 return $this->requestedFields;
302 }
303
304 /**
305 * @param array|null $requestedFields
306 */
307 public function setRequestedFields($requestedFields) {
308 $this->requestedFields = $requestedFields;
309 }
310
311 /**
312 * @return array
313 */
314 public function getReturnProperties() {
315 return array_merge($this->returnProperties, $this->getAdditionalRequestedReturnProperties(), $this->getAdditionalFieldsForSameAddressMerge());
316 }
317
318 /**
319 * @param array $returnProperties
320 */
321 public function setReturnProperties($returnProperties) {
322 $this->returnProperties = $returnProperties;
323 }
324
325 /**
326 * @return array
327 */
328 public function getRelationshipTypes() {
329 return $this->relationshipTypes;
330 }
331
332 /**
333 */
334 public function setRelationshipTypes() {
335 $this->relationshipTypes = CRM_Contact_BAO_Relationship::getContactRelationshipType(
336 NULL,
337 NULL,
338 NULL,
339 NULL,
340 TRUE,
341 'name',
342 FALSE
343 );
344 }
345
346 /**
347 * Set the value for a relationship type field.
348 *
349 * In this case we are building up an array of properties for a related contact.
350 *
351 * These may be used for direct exporting or for merge to household depending on the
352 * options selected.
353 *
354 * @param string $relationshipType
355 * @param int $contactID
356 * @param string $field
357 * @param string $value
358 */
359 public function setRelationshipValue($relationshipType, $contactID, $field, $value) {
360 $this->relatedContactValues[$relationshipType][$contactID][$field] = $value;
361 if ($field === 'id') {
362 $this->householdsToSkip[] = $value;
363 }
364 }
365
366 /**
367 * Get the value for a relationship type field.
368 *
369 * In this case we are building up an array of properties for a related contact.
370 *
371 * These may be used for direct exporting or for merge to household depending on the
372 * options selected.
373 *
374 * @param string $relationshipType
375 * @param int $contactID
376 * @param string $field
377 *
378 * @return string
379 */
380 public function getRelationshipValue($relationshipType, $contactID, $field) {
381 return isset($this->relatedContactValues[$relationshipType][$contactID][$field]) ? $this->relatedContactValues[$relationshipType][$contactID][$field] : '';
382 }
383
384 /**
385 * Get the id of the related household.
386 *
387 * @param int $contactID
388 * @param string $relationshipType
389 *
390 * @return int
391 */
392 public function getRelatedHouseholdID($contactID, $relationshipType) {
393 return $this->relatedContactValues[$relationshipType][$contactID]['id'];
394 }
395
396 /**
397 * Has the household already been exported.
398 *
399 * @param int $housholdContactID
400 *
401 * @return bool
402 */
403 public function isHouseholdExported($housholdContactID) {
404 return isset($this->exportedHouseholds[$housholdContactID]);
405
406 }
407
408 /**
409 * @return bool
410 */
411 public function isMergeSameHousehold() {
412 return $this->isMergeSameHousehold;
413 }
414
415 /**
416 * @param bool $isMergeSameHousehold
417 */
418 public function setIsMergeSameHousehold($isMergeSameHousehold) {
419 $this->isMergeSameHousehold = $isMergeSameHousehold;
420 }
421
422 /**
423 * Return relationship types for household merge.
424 *
425 * @return mixed
426 */
427 public function getHouseholdRelationshipTypes() {
428 if (!$this->isMergeSameHousehold()) {
429 return [];
430 }
431 return [
432 CRM_Utils_Array::key('Household Member of', $this->getRelationshipTypes()),
433 CRM_Utils_Array::key('Head of Household for', $this->getRelationshipTypes()),
434 ];
435 }
436
437 /**
438 * @param $fieldName
439 * @return bool
440 */
441 public function isRelationshipTypeKey($fieldName) {
442 return array_key_exists($fieldName, $this->relationshipTypes);
443 }
444
445 /**
446 * @param $fieldName
447 * @return bool
448 */
449 public function isHouseholdMergeRelationshipTypeKey($fieldName) {
450 return in_array($fieldName, $this->getHouseholdRelationshipTypes());
451 }
452
453 /**
454 * @return string
455 */
456 public function getQueryOperator() {
457 return $this->queryOperator;
458 }
459
460 /**
461 * @param string $queryOperator
462 */
463 public function setQueryOperator($queryOperator) {
464 $this->queryOperator = $queryOperator;
465 }
466
467 /**
468 * @return array
469 */
470 public function getQueryFields() {
471 return $this->queryFields;
472 }
473
474 /**
475 * @param array $queryFields
476 */
477 public function setQueryFields($queryFields) {
478 // legacy hacks - we add these to queryFields because this
479 // pseudometadata is currently required.
480 $queryFields['im_provider']['pseudoconstant']['var'] = 'imProviders';
481 $queryFields['country']['context'] = 'country';
482 $queryFields['world_region']['context'] = 'country';
483 $queryFields['state_province']['context'] = 'province';
484 $this->queryFields = $queryFields;
485 }
486
487 /**
488 * @return int
489 */
490 public function getQueryMode() {
491 return $this->queryMode;
492 }
493
494 /**
495 * Set the query mode based on the export mode.
496 */
497 public function setQueryMode() {
498
499 switch ($this->getExportMode()) {
500 case CRM_Export_Form_Select::CONTRIBUTE_EXPORT:
501 $this->queryMode = CRM_Contact_BAO_Query::MODE_CONTRIBUTE;
502 break;
503
504 case CRM_Export_Form_Select::EVENT_EXPORT:
505 $this->queryMode = CRM_Contact_BAO_Query::MODE_EVENT;
506 break;
507
508 case CRM_Export_Form_Select::MEMBER_EXPORT:
509 $this->queryMode = CRM_Contact_BAO_Query::MODE_MEMBER;
510 break;
511
512 case CRM_Export_Form_Select::PLEDGE_EXPORT:
513 $this->queryMode = CRM_Contact_BAO_Query::MODE_PLEDGE;
514 break;
515
516 case CRM_Export_Form_Select::CASE_EXPORT:
517 $this->queryMode = CRM_Contact_BAO_Query::MODE_CASE;
518 break;
519
520 case CRM_Export_Form_Select::GRANT_EXPORT:
521 $this->queryMode = CRM_Contact_BAO_Query::MODE_GRANT;
522 break;
523
524 case CRM_Export_Form_Select::ACTIVITY_EXPORT:
525 $this->queryMode = CRM_Contact_BAO_Query::MODE_ACTIVITY;
526 break;
527
528 default:
529 $this->queryMode = CRM_Contact_BAO_Query::MODE_CONTACTS;
530 }
531 }
532
533 /**
534 * @return int
535 */
536 public function getExportMode() {
537 return $this->exportMode;
538 }
539
540 /**
541 * @param int $exportMode
542 */
543 public function setExportMode($exportMode) {
544 $this->exportMode = $exportMode;
545 }
546
547 /**
548 * Get the name for the export file.
549 *
550 * @return string
551 */
552 public function getExportFileName() {
553 switch ($this->getExportMode()) {
554 case CRM_Export_Form_Select::CONTACT_EXPORT:
555 return ts('CiviCRM Contact Search');
556
557 case CRM_Export_Form_Select::CONTRIBUTE_EXPORT:
558 return ts('CiviCRM Contribution Search');
559
560 case CRM_Export_Form_Select::MEMBER_EXPORT:
561 return ts('CiviCRM Member Search');
562
563 case CRM_Export_Form_Select::EVENT_EXPORT:
564 return ts('CiviCRM Participant Search');
565
566 case CRM_Export_Form_Select::PLEDGE_EXPORT:
567 return ts('CiviCRM Pledge Search');
568
569 case CRM_Export_Form_Select::CASE_EXPORT:
570 return ts('CiviCRM Case Search');
571
572 case CRM_Export_Form_Select::GRANT_EXPORT:
573 return ts('CiviCRM Grant Search');
574
575 case CRM_Export_Form_Select::ACTIVITY_EXPORT:
576 return ts('CiviCRM Activity Search');
577
578 default:
579 // Legacy code suggests the value could be 'financial' - ie. something
580 // other than what should be accepted. However, I suspect that this line is
581 // never hit.
582 return ts('CiviCRM Search');
583 }
584 }
585
586 /**
587 * Get the label for the header row based on the field to output.
588 *
589 * @param string $field
590 *
591 * @return string
592 */
593 public function getHeaderForRow($field) {
594 if (substr($field, -11) == 'campaign_id') {
595 // @todo - set this correctly in the xml rather than here.
596 // This will require a generalised handling cleanup
597 return ts('Campaign ID');
598 }
599 if ($this->isMergeSameHousehold() && $field === 'id') {
600 return ts('Household ID');
601 }
602 elseif (isset($this->getQueryFields()[$field]['title'])) {
603 return $this->getQueryFields()[$field]['title'];
604 }
605 elseif ($this->isExportPaymentFields() && array_key_exists($field, $this->getcomponentPaymentFields())) {
606 return CRM_Utils_Array::value($field, $this->getcomponentPaymentFields());
607 }
608 else {
609 return $field;
610 }
611 }
612
613 /**
614 * @param $params
615 * @param $order
616 * @param $returnProperties
617 * @return array
618 */
619 public function runQuery($params, $order, $returnProperties) {
620 $addressWhere = '';
621 $params = array_merge($params, $this->getWhereParams());
622 if ($this->isPostalableOnly) {
623 if (array_key_exists('street_address', $returnProperties)) {
624 $addressWhere = " civicrm_address.street_address <> ''";
625 if (array_key_exists('supplemental_address_1', $returnProperties)) {
626 // We need this to be an OR rather than AND on the street_address so, hack it in.
627 $addressOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
628 'address_options', TRUE, NULL, TRUE
629 );
630 if (!empty($addressOptions['supplemental_address_1'])) {
631 $addressWhere .= " OR civicrm_address.supplemental_address_1 <> ''";
632 }
633 }
634 $addressWhere = ' AND (' . $addressWhere . ')';
635 }
636 }
637 $query = new CRM_Contact_BAO_Query($params, $returnProperties, NULL,
638 FALSE, FALSE, $this->getQueryMode(),
639 FALSE, TRUE, TRUE, NULL, $this->getQueryOperator()
640 );
641
642 //sort by state
643 //CRM-15301
644 $query->_sort = $order;
645 list($select, $from, $where, $having) = $query->query();
646 $this->setQueryFields($query->_fields);
647 return [$query, $select, $from, $where . $addressWhere, $having];
648 }
649
650 /**
651 * Add a row to the specification for how to output data.
652 *
653 * @param string $key
654 * @param string $relationshipType
655 * @param string $locationType
656 * @param int $entityTypeID phone_type_id or provider_id for phone or im fields.
657 */
658 public function addOutputSpecification($key, $relationshipType = NULL, $locationType = NULL, $entityTypeID = NULL) {
659 $entityLabel = '';
660 if ($entityTypeID) {
661 if ($key === 'phone') {
662 $entityLabel = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_Phone', 'phone_type_id', $entityTypeID);
663 }
664 if ($key === 'im') {
665 $entityLabel = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_IM', 'provider_id', $entityTypeID);
666 }
667 }
668
669 // These oddly constructed keys are for legacy reasons. Altering them will affect test success
670 // but in time it may be good to rationalise them.
671 $label = $this->getOutputSpecificationLabel($key, $relationshipType, $locationType, $entityLabel);
672 $index = $this->getOutputSpecificationIndex($key, $relationshipType, $locationType, $entityLabel);
673 $fieldKey = $this->getOutputSpecificationFieldKey($key, $relationshipType, $locationType, $entityLabel);
674
675 $this->outputSpecification[$index]['header'] = $label;
676 $this->outputSpecification[$index]['sql_columns'] = $this->getSqlColumnDefinition($fieldKey, $key);
677
678 if ($relationshipType && $this->isHouseholdMergeRelationshipTypeKey($relationshipType)) {
679 $this->setColumnAsCalculationOnly($index);
680 }
681 $this->outputSpecification[$index]['metadata'] = $this->getMetaDataForField($key);
682 }
683
684 /**
685 * Get the metadata for the given field.
686 *
687 * @param $key
688 *
689 * @return array
690 */
691 public function getMetaDataForField($key) {
692 $mappings = ['contact_id' => 'id'];
693 if (isset($this->getQueryFields()[$key])) {
694 return $this->getQueryFields()[$key];
695 }
696 if (isset($mappings[$key])) {
697 return $this->getQueryFields()[$mappings[$key]];
698 }
699 return [];
700 }
701
702 /**
703 * @param $key
704 */
705 public function setSqlColumnDefn($key) {
706 $this->outputSpecification[$this->getMungedFieldName($key)]['sql_columns'] = $this->getSqlColumnDefinition($key, $this->getMungedFieldName($key));
707 }
708
709 /**
710 * Mark a column as only required for calculations.
711 *
712 * Do not include the row with headers.
713 *
714 * @param string $column
715 */
716 public function setColumnAsCalculationOnly($column) {
717 $this->outputSpecification[$column]['do_not_output_to_csv'] = TRUE;
718 }
719
720 /**
721 * @return array
722 */
723 public function getHeaderRows() {
724 $headerRows = [];
725 foreach ($this->outputSpecification as $key => $spec) {
726 if (empty($spec['do_not_output_to_csv'])) {
727 $headerRows[] = $spec['header'];
728 }
729 }
730 return $headerRows;
731 }
732
733 /**
734 * @return array
735 */
736 public function getSQLColumns() {
737 $sqlColumns = [];
738 foreach ($this->outputSpecification as $key => $spec) {
739 if (empty($spec['do_not_output_to_sql'])) {
740 $sqlColumns[$key] = $spec['sql_columns'];
741 }
742 }
743 return $sqlColumns;
744 }
745
746 /**
747 * @return array
748 */
749 public function getMetadata() {
750 $metadata = [];
751 foreach ($this->outputSpecification as $key => $spec) {
752 $metadata[$key] = $spec['metadata'];
753 }
754 return $metadata;
755 }
756
757 /**
758 * Build the row for output.
759 *
760 * @param \CRM_Contact_BAO_Query $query
761 * @param CRM_Core_DAO $iterationDAO
762 * @param array $outputColumns
763 * @param $metadata
764 * @param $paymentDetails
765 * @param $addPaymentHeader
766 * @param $paymentTableId
767 *
768 * @return array|bool
769 */
770 public function buildRow($query, $iterationDAO, $outputColumns, $metadata, $paymentDetails, $addPaymentHeader, $paymentTableId) {
771 if ($this->isHouseholdToSkip($iterationDAO->contact_id)) {
772 return FALSE;
773 }
774 $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id');
775 $imProviders = CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id');
776
777 $row = [];
778 $householdMergeRelationshipType = $this->getHouseholdMergeTypeForRow($iterationDAO->contact_id);
779 if ($householdMergeRelationshipType) {
780 $householdID = $this->getRelatedHouseholdID($iterationDAO->contact_id, $householdMergeRelationshipType);
781 if ($this->isHouseholdExported($householdID)) {
782 return FALSE;
783 }
784 foreach (array_keys($outputColumns) as $column) {
785 $row[$column] = $this->getRelationshipValue($householdMergeRelationshipType, $iterationDAO->contact_id, $column);
786 }
787 $this->markHouseholdExported($householdID);
788 return $row;
789 }
790
791 $query->convertToPseudoNames($iterationDAO);
792
793 //first loop through output columns so that we return what is required, and in same order.
794 foreach ($outputColumns as $field => $value) {
795 // add im_provider to $dao object
796 if ($field == 'im_provider' && property_exists($iterationDAO, 'provider_id')) {
797 $iterationDAO->im_provider = $iterationDAO->provider_id;
798 }
799
800 //build row values (data)
801 $fieldValue = NULL;
802 if (property_exists($iterationDAO, $field)) {
803 $fieldValue = $iterationDAO->$field;
804 // to get phone type from phone type id
805 if ($field == 'phone_type_id' && isset($phoneTypes[$fieldValue])) {
806 $fieldValue = $phoneTypes[$fieldValue];
807 }
808 elseif ($field == 'provider_id' || $field == 'im_provider') {
809 $fieldValue = CRM_Utils_Array::value($fieldValue, $imProviders);
810 }
811 elseif (strstr($field, 'master_id')) {
812 $masterAddressId = NULL;
813 if (isset($iterationDAO->$field)) {
814 $masterAddressId = $iterationDAO->$field;
815 }
816 // get display name of contact that address is shared.
817 $fieldValue = CRM_Contact_BAO_Contact::getMasterDisplayName($masterAddressId);
818 }
819 }
820
821 if ($this->isRelationshipTypeKey($field)) {
822 $this->buildRelationshipFieldsForRow($row, $iterationDAO->contact_id, $value, $field);
823 }
824 else {
825 $row[$field] = $this->getTransformedFieldValue($field, $iterationDAO, $fieldValue, $metadata, $paymentDetails);
826 }
827 }
828
829 // If specific payment fields have been selected for export, payment
830 // data will already be in $row. Otherwise, add payment related
831 // information, if appropriate.
832 if ($addPaymentHeader) {
833 if (!$this->isExportSpecifiedPaymentFields()) {
834 $nullContributionDetails = array_fill_keys(array_keys($this->getPaymentHeaders()), NULL);
835 if ($this->isExportPaymentFields()) {
836 $paymentData = CRM_Utils_Array::value($row[$paymentTableId], $paymentDetails);
837 if (!is_array($paymentData) || empty($paymentData)) {
838 $paymentData = $nullContributionDetails;
839 }
840 $row = array_merge($row, $paymentData);
841 }
842 elseif (!empty($paymentDetails)) {
843 $row = array_merge($row, $nullContributionDetails);
844 }
845 }
846 }
847 //remove organization name for individuals if it is set for current employer
848 if (!empty($row['contact_type']) &&
849 $row['contact_type'] == 'Individual' && array_key_exists('organization_name', $row)
850 ) {
851 $row['organization_name'] = '';
852 }
853 return $row;
854 }
855
856 /**
857 * If this row has a household whose details we should use get the relationship type key.
858 *
859 * @param $contactID
860 *
861 * @return bool
862 */
863 public function getHouseholdMergeTypeForRow($contactID) {
864 if (!$this->isMergeSameHousehold()) {
865 return FALSE;
866 }
867 foreach ($this->getHouseholdRelationshipTypes() as $relationshipType) {
868 if (isset($this->relatedContactValues[$relationshipType][$contactID])) {
869 return $relationshipType;
870 }
871 }
872 }
873
874 /**
875 * Mark the given household as already exported.
876 *
877 * @param $householdID
878 */
879 public function markHouseholdExported($householdID) {
880 $this->exportedHouseholds[$householdID] = $householdID;
881 }
882
883 /**
884 * @param $field
885 * @param $iterationDAO
886 * @param $fieldValue
887 * @param $metadata
888 * @param $paymentDetails
889 *
890 * @return string
891 */
892 public function getTransformedFieldValue($field, $iterationDAO, $fieldValue, $metadata, $paymentDetails) {
893
894 $i18n = CRM_Core_I18n::singleton();
895 if ($field == 'id') {
896 return $iterationDAO->contact_id;
897 // special case for calculated field
898 }
899 elseif ($field == 'source_contact_id') {
900 return $iterationDAO->contact_id;
901 }
902 elseif ($field == 'pledge_balance_amount') {
903 return $iterationDAO->pledge_amount - $iterationDAO->pledge_total_paid;
904 // special case for calculated field
905 }
906 elseif ($field == 'pledge_next_pay_amount') {
907 return $iterationDAO->pledge_next_pay_amount + $iterationDAO->pledge_outstanding_amount;
908 }
909 elseif (isset($fieldValue) &&
910 $fieldValue != ''
911 ) {
912 //check for custom data
913 if ($cfID = CRM_Core_BAO_CustomField::getKeyID($field)) {
914 return CRM_Core_BAO_CustomField::displayValue($fieldValue, $cfID);
915 }
916
917 elseif (in_array($field, [
918 'email_greeting',
919 'postal_greeting',
920 'addressee',
921 ])) {
922 //special case for greeting replacement
923 $fldValue = "{$field}_display";
924 return $iterationDAO->$fldValue;
925 }
926 else {
927 //normal fields with a touch of CRM-3157
928 switch ($field) {
929 case 'country':
930 case 'world_region':
931 return $i18n->crm_translate($fieldValue, ['context' => 'country']);
932
933 case 'state_province':
934 return $i18n->crm_translate($fieldValue, ['context' => 'province']);
935
936 case 'gender':
937 case 'preferred_communication_method':
938 case 'preferred_mail_format':
939 case 'communication_style':
940 return $i18n->crm_translate($fieldValue);
941
942 default:
943 if (isset($metadata[$field])) {
944 // No I don't know why we do it this way & whether we could
945 // make better use of pseudoConstants.
946 if (!empty($metadata[$field]['context'])) {
947 return $i18n->crm_translate($fieldValue, $metadata[$field]);
948 }
949 if (!empty($metadata[$field]['pseudoconstant'])) {
950 if (!empty($metadata[$field]['bao'])) {
951 return CRM_Core_PseudoConstant::getLabel($metadata[$field]['bao'], $metadata[$field]['name'], $fieldValue);
952 }
953 // This is not our normal syntax for pseudoconstants but I am a bit loath to
954 // call an external function until sure it is not increasing php processing given this
955 // may be iterated 100,000 times & we already have the $imProvider var loaded.
956 // That can be next refactor...
957 // Yes - definitely feeling hatred for this bit of code - I know you will beat me up over it's awfulness
958 // but I have to reach a stable point....
959 $varName = $metadata[$field]['pseudoconstant']['var'];
960 if ($varName === 'imProviders') {
961 return CRM_Core_PseudoConstant::getLabel('CRM_Core_DAO_IM', 'provider_id', $fieldValue);
962 }
963 if ($varName === 'phoneTypes') {
964 return CRM_Core_PseudoConstant::getLabel('CRM_Core_DAO_Phone', 'phone_type_id', $fieldValue);
965 }
966 }
967
968 }
969 return $fieldValue;
970 }
971 }
972 }
973 elseif ($this->isExportSpecifiedPaymentFields() && array_key_exists($field, $this->getcomponentPaymentFields())) {
974 $paymentTableId = $this->getPaymentTableID();
975 $paymentData = CRM_Utils_Array::value($iterationDAO->$paymentTableId, $paymentDetails);
976 $payFieldMapper = [
977 'componentPaymentField_total_amount' => 'total_amount',
978 'componentPaymentField_contribution_status' => 'contribution_status',
979 'componentPaymentField_payment_instrument' => 'pay_instru',
980 'componentPaymentField_transaction_id' => 'trxn_id',
981 'componentPaymentField_received_date' => 'receive_date',
982 ];
983 return CRM_Utils_Array::value($payFieldMapper[$field], $paymentData, '');
984 }
985 else {
986 // if field is empty or null
987 return '';
988 }
989 }
990
991 /**
992 * Get array of fields to return, over & above those defined in the main contact exportable fields.
993 *
994 * These include export mode specific fields & some fields apparently required as 'exportableFields'
995 * but not returned by the function of the same name.
996 *
997 * @return array
998 * Array of fields to return in the format ['field_name' => 1,...]
999 */
1000 public function getAdditionalReturnProperties() {
1001 if ($this->getQueryMode() === CRM_Contact_BAO_Query::MODE_CONTACTS) {
1002 $componentSpecificFields = [];
1003 }
1004 else {
1005 $componentSpecificFields = CRM_Contact_BAO_Query::defaultReturnProperties($this->getQueryMode());
1006 }
1007 if ($this->getQueryMode() === CRM_Contact_BAO_Query::MODE_PLEDGE) {
1008 $componentSpecificFields = array_merge($componentSpecificFields, CRM_Pledge_BAO_Query::extraReturnProperties($this->getQueryMode()));
1009 unset($componentSpecificFields['contribution_status_id']);
1010 unset($componentSpecificFields['pledge_status_id']);
1011 unset($componentSpecificFields['pledge_payment_status_id']);
1012 }
1013 if ($this->getQueryMode() === CRM_Contact_BAO_Query::MODE_CASE) {
1014 $componentSpecificFields = array_merge($componentSpecificFields, CRM_Case_BAO_Query::extraReturnProperties($this->getQueryMode()));
1015 }
1016 if ($this->getQueryMode() === CRM_Contact_BAO_Query::MODE_CONTRIBUTE) {
1017 $componentSpecificFields = array_merge($componentSpecificFields, CRM_Contribute_BAO_Query::softCreditReturnProperties(TRUE));
1018 unset($componentSpecificFields['contribution_status_id']);
1019 }
1020 return $componentSpecificFields;
1021 }
1022
1023 /**
1024 * Should payment fields be appended to the export.
1025 *
1026 * (This is pretty hacky so hopefully this function won't last long - notice
1027 * how obviously it should be part of the above function!).
1028 */
1029 public function isExportPaymentFields() {
1030 if ($this->getRequestedFields() === NULL
1031 && in_array($this->getQueryMode(), [
1032 CRM_Contact_BAO_Query::MODE_EVENT,
1033 CRM_Contact_BAO_Query::MODE_MEMBER,
1034 CRM_Contact_BAO_Query::MODE_PLEDGE,
1035 ])) {
1036 return TRUE;
1037 }
1038 elseif ($this->isExportSpecifiedPaymentFields()) {
1039 return TRUE;
1040 }
1041 return FALSE;
1042 }
1043
1044 /**
1045 * Has specific payment fields been requested (as opposed to via all fields).
1046 *
1047 * If specific fields have been requested then they get added at various points.
1048 *
1049 * @return bool
1050 */
1051 public function isExportSpecifiedPaymentFields() {
1052 if ($this->getRequestedFields() !== NULL && $this->hasRequestedComponentPaymentFields()) {
1053 return TRUE;
1054 }
1055 }
1056
1057 /**
1058 * Get the name of the id field in the table that connects contributions to the export entity.
1059 */
1060 public function getPaymentTableID() {
1061 if ($this->getRequestedFields() === NULL) {
1062 $mapping = [
1063 CRM_Contact_BAO_Query::MODE_EVENT => 'participant_id',
1064 CRM_Contact_BAO_Query::MODE_MEMBER => 'membership_id',
1065 CRM_Contact_BAO_Query::MODE_PLEDGE => 'pledge_payment_id',
1066 ];
1067 return isset($mapping[$this->getQueryMode()]) ? $mapping[$this->getQueryMode()] : '';
1068 }
1069 elseif ($this->hasRequestedComponentPaymentFields()) {
1070 return 'participant_id';
1071 }
1072 return FALSE;
1073 }
1074
1075 /**
1076 * Have component payment fields been requested.
1077 *
1078 * @return bool
1079 */
1080 protected function hasRequestedComponentPaymentFields() {
1081 if ($this->getQueryMode() === CRM_Contact_BAO_Query::MODE_EVENT) {
1082 $participantPaymentFields = array_intersect_key($this->getComponentPaymentFields(), $this->getReturnProperties());
1083 if (!empty($participantPaymentFields)) {
1084 return TRUE;
1085 }
1086 }
1087 return FALSE;
1088 }
1089
1090 /**
1091 * Get fields that indicate payment fields have been requested for a component.
1092 *
1093 * Ideally this should be protected but making it temporarily public helps refactoring..
1094 *
1095 * @return array
1096 */
1097 public function getComponentPaymentFields() {
1098 return [
1099 'componentPaymentField_total_amount' => ts('Total Amount'),
1100 'componentPaymentField_contribution_status' => ts('Contribution Status'),
1101 'componentPaymentField_received_date' => ts('Date Received'),
1102 'componentPaymentField_payment_instrument' => ts('Payment Method'),
1103 'componentPaymentField_transaction_id' => ts('Transaction ID'),
1104 ];
1105 }
1106
1107 /**
1108 * Get headers for payment fields.
1109 *
1110 * Returns an array of contribution fields when the entity supports payment fields and specific fields
1111 * are not specified. This is a transitional function for refactoring legacy code.
1112 */
1113 public function getPaymentHeaders() {
1114 if ($this->isExportPaymentFields() && !$this->isExportSpecifiedPaymentFields()) {
1115 return $this->getcomponentPaymentFields();
1116 }
1117 return [];
1118 }
1119
1120 /**
1121 * Get the default properties when not specified.
1122 *
1123 * In the UI this appears as 'Primary fields only' but in practice it's
1124 * most of the kitchen sink and the hallway closet thrown in.
1125 *
1126 * Since CRM-952 custom fields are excluded, but no other form of mercy is shown.
1127 *
1128 * @return array
1129 */
1130 public function getDefaultReturnProperties() {
1131 $returnProperties = [];
1132 $fields = CRM_Contact_BAO_Contact::exportableFields('All', TRUE, TRUE);
1133 $skippedFields = ($this->getQueryMode() === CRM_Contact_BAO_Query::MODE_CONTACTS) ? [] : [
1134 'groups',
1135 'tags',
1136 'notes',
1137 ];
1138
1139 foreach ($fields as $key => $var) {
1140 if ($key && (substr($key, 0, 6) != 'custom') && !in_array($key, $skippedFields)) {
1141 $returnProperties[$key] = 1;
1142 }
1143 }
1144 $returnProperties = array_merge($returnProperties, $this->getAdditionalReturnProperties());
1145 return $returnProperties;
1146 }
1147
1148 /**
1149 * Add the field to relationship return properties & return it.
1150 *
1151 * This function is doing both setting & getting which is yuck but it is an interim
1152 * refactor.
1153 *
1154 * @param array $value
1155 * @param string $relationshipKey
1156 *
1157 * @return array
1158 */
1159 public function setRelationshipReturnProperties($value, $relationshipKey) {
1160 $relPhoneTypeId = $relIMProviderId = NULL;
1161 if (!empty($value[2])) {
1162 $relationField = CRM_Utils_Array::value(2, $value);
1163 if (trim(CRM_Utils_Array::value(3, $value))) {
1164 $relLocTypeId = CRM_Utils_Array::value(3, $value);
1165 }
1166 else {
1167 $relLocTypeId = 'Primary';
1168 }
1169
1170 if ($relationField == 'phone') {
1171 $relPhoneTypeId = CRM_Utils_Array::value(4, $value);
1172 }
1173 elseif ($relationField == 'im') {
1174 $relIMProviderId = CRM_Utils_Array::value(4, $value);
1175 }
1176 }
1177 elseif (!empty($value[4])) {
1178 $relationField = CRM_Utils_Array::value(4, $value);
1179 $relLocTypeId = CRM_Utils_Array::value(5, $value);
1180 if ($relationField == 'phone') {
1181 $relPhoneTypeId = CRM_Utils_Array::value(6, $value);
1182 }
1183 elseif ($relationField == 'im') {
1184 $relIMProviderId = CRM_Utils_Array::value(6, $value);
1185 }
1186 }
1187 if (in_array($relationField, $this->getValidLocationFields()) && is_numeric($relLocTypeId)) {
1188 $locationName = CRM_Core_PseudoConstant::getName('CRM_Core_BAO_Address', 'location_type_id', $relLocTypeId);
1189 if ($relPhoneTypeId) {
1190 $this->relationshipReturnProperties[$relationshipKey]['location'][$locationName]['phone-' . $relPhoneTypeId] = 1;
1191 }
1192 elseif ($relIMProviderId) {
1193 $this->relationshipReturnProperties[$relationshipKey]['location'][$locationName]['im-' . $relIMProviderId] = 1;
1194 }
1195 else {
1196 $this->relationshipReturnProperties[$relationshipKey]['location'][$locationName][$relationField] = 1;
1197 }
1198 }
1199 else {
1200 $this->relationshipReturnProperties[$relationshipKey][$relationField] = 1;
1201 }
1202 return $this->relationshipReturnProperties[$relationshipKey];
1203 }
1204
1205 /**
1206 * Add the main return properties to the household merge properties if needed for merging.
1207 *
1208 * If we are using household merge we need to add these to the relationship properties to
1209 * be retrieved.
1210 *
1211 * @param $returnProperties
1212 */
1213 public function setHouseholdMergeReturnProperties($returnProperties) {
1214 $returnProperties = array_diff_key($returnProperties, array_fill_keys(['location_type', 'im_provider'], 1));
1215 foreach ($this->getHouseholdRelationshipTypes() as $householdRelationshipType) {
1216 $this->relationshipReturnProperties[$householdRelationshipType] = $returnProperties;
1217 }
1218 }
1219
1220 /**
1221 * Get the default location fields to request.
1222 *
1223 * @return array
1224 */
1225 public function getValidLocationFields() {
1226 return [
1227 'street_address',
1228 'supplemental_address_1',
1229 'supplemental_address_2',
1230 'supplemental_address_3',
1231 'city',
1232 'postal_code',
1233 'postal_code_suffix',
1234 'geo_code_1',
1235 'geo_code_2',
1236 'state_province',
1237 'country',
1238 'phone',
1239 'email',
1240 'im',
1241 ];
1242 }
1243
1244 /**
1245 * Get the sql column definition for the given field.
1246 *
1247 * @param string $fieldName
1248 * @param string $columnName
1249 *
1250 * @return mixed
1251 */
1252 public function getSqlColumnDefinition($fieldName, $columnName) {
1253
1254 // early exit for master_id, CRM-12100
1255 // in the DB it is an ID, but in the export, we retrive the display_name of the master record
1256 // also for current_employer, CRM-16939
1257 if ($columnName == 'master_id' || $columnName == 'current_employer') {
1258 return "$fieldName varchar(128)";
1259 }
1260
1261 if (substr($fieldName, -11) == 'campaign_id') {
1262 // CRM-14398
1263 return "$fieldName varchar(128)";
1264 }
1265
1266 $queryFields = $this->getQueryFields();
1267 $lookUp = ['prefix_id', 'suffix_id'];
1268 // set the sql columns
1269 if (isset($queryFields[$columnName]['type'])) {
1270 switch ($queryFields[$columnName]['type']) {
1271 case CRM_Utils_Type::T_INT:
1272 case CRM_Utils_Type::T_BOOLEAN:
1273 if (in_array($columnName, $lookUp)) {
1274 return "$fieldName varchar(255)";
1275 }
1276 else {
1277 return "$fieldName varchar(16)";
1278 }
1279
1280 case CRM_Utils_Type::T_STRING:
1281 if (isset($queryFields[$columnName]['maxlength'])) {
1282 return "$fieldName varchar({$queryFields[$columnName]['maxlength']})";
1283 }
1284 else {
1285 return "$fieldName varchar(255)";
1286 }
1287
1288 case CRM_Utils_Type::T_TEXT:
1289 case CRM_Utils_Type::T_LONGTEXT:
1290 case CRM_Utils_Type::T_BLOB:
1291 case CRM_Utils_Type::T_MEDIUMBLOB:
1292 return "$fieldName longtext";
1293
1294 case CRM_Utils_Type::T_FLOAT:
1295 case CRM_Utils_Type::T_ENUM:
1296 case CRM_Utils_Type::T_DATE:
1297 case CRM_Utils_Type::T_TIME:
1298 case CRM_Utils_Type::T_TIMESTAMP:
1299 case CRM_Utils_Type::T_MONEY:
1300 case CRM_Utils_Type::T_EMAIL:
1301 case CRM_Utils_Type::T_URL:
1302 case CRM_Utils_Type::T_CCNUM:
1303 default:
1304 return "$fieldName varchar(32)";
1305 }
1306 }
1307 else {
1308 if (substr($fieldName, -3, 3) == '_id') {
1309 return "$fieldName varchar(255)";
1310 }
1311 elseif (substr($fieldName, -5, 5) == '_note') {
1312 return "$fieldName text";
1313 }
1314 else {
1315 $changeFields = [
1316 'groups',
1317 'tags',
1318 'notes',
1319 ];
1320
1321 if (in_array($fieldName, $changeFields)) {
1322 return "$fieldName text";
1323 }
1324 else {
1325 // set the sql columns for custom data
1326 if (isset($queryFields[$columnName]['data_type'])) {
1327
1328 switch ($queryFields[$columnName]['data_type']) {
1329 case 'String':
1330 // May be option labels, which could be up to 512 characters
1331 $length = max(512, CRM_Utils_Array::value('text_length', $queryFields[$columnName]));
1332 return "$fieldName varchar($length)";
1333
1334 case 'Country':
1335 case 'StateProvince':
1336 case 'Link':
1337 return "$fieldName varchar(255)";
1338
1339 case 'Memo':
1340 return "$fieldName text";
1341
1342 default:
1343 return "$fieldName varchar(255)";
1344 }
1345 }
1346 else {
1347 return "$fieldName text";
1348 }
1349 }
1350 }
1351 }
1352 }
1353
1354 /**
1355 * Get the munged field name.
1356 *
1357 * @param string $field
1358 * @return string
1359 */
1360 public function getMungedFieldName($field) {
1361 $fieldName = CRM_Utils_String::munge(strtolower($field), '_', 64);
1362 if ($fieldName == 'id') {
1363 $fieldName = 'civicrm_primary_id';
1364 }
1365 return $fieldName;
1366 }
1367
1368 /**
1369 * In order to respect the history of this class we need to index kinda illogically.
1370 *
1371 * On the bright side - this stuff is tested within a nano-byte of it's life.
1372 *
1373 * e.g '2-a-b_Home-City'
1374 *
1375 * @param string $key
1376 * @param string $relationshipType
1377 * @param string $locationType
1378 * @param $entityLabel
1379 *
1380 * @return string
1381 */
1382 protected function getOutputSpecificationIndex($key, $relationshipType, $locationType, $entityLabel) {
1383 if ($entityLabel || $key === 'im') {
1384 // Just cos that's the history...
1385 if ($key !== 'master_id') {
1386 $key = $this->getHeaderForRow($key);
1387 }
1388 }
1389 if (!$relationshipType || $key !== 'id') {
1390 $key = $this->getMungedFieldName($key);
1391 }
1392 return $this->getMungedFieldName(
1393 ($relationshipType ? ($relationshipType . '_') : '')
1394 . ($locationType ? ($locationType . '_') : '')
1395 . $key
1396 . ($entityLabel ? ('_' . $entityLabel) : '')
1397 );
1398 }
1399
1400 /**
1401 * Get the compiled label for the column.
1402 *
1403 * e.g 'Gender', 'Employee Of-Home-city'
1404 *
1405 * @param string $key
1406 * @param string $relationshipType
1407 * @param string $locationType
1408 * @param string $entityLabel
1409 *
1410 * @return string
1411 */
1412 protected function getOutputSpecificationLabel($key, $relationshipType, $locationType, $entityLabel) {
1413 return ($relationshipType ? $this->getRelationshipTypes()[$relationshipType] . '-' : '')
1414 . ($locationType ? $locationType . '-' : '')
1415 . $this->getHeaderForRow($key)
1416 . ($entityLabel ? '-' . $entityLabel : '');
1417 }
1418
1419 /**
1420 * Get the mysql field name key.
1421 *
1422 * This key is locked in by tests but the reasons for the specific conventions -
1423 * ie. headings are used for keying fields in some cases, are likely
1424 * accidental rather than deliberate.
1425 *
1426 * This key is used for the output sql array.
1427 *
1428 * @param string $key
1429 * @param $relationshipType
1430 * @param $locationType
1431 * @param $entityLabel
1432 *
1433 * @return string
1434 */
1435 protected function getOutputSpecificationFieldKey($key, $relationshipType, $locationType, $entityLabel) {
1436 if ($entityLabel || $key === 'im') {
1437 if ($key !== 'state_province' && $key !== 'id') {
1438 // @todo - test removing this - indexing by $key should be fine...
1439 $key = $this->getHeaderForRow($key);
1440 }
1441 }
1442 if (!$relationshipType || $key !== 'id') {
1443 $key = $this->getMungedFieldName($key);
1444 }
1445 $fieldKey = $this->getMungedFieldName(
1446 ($relationshipType ? ($relationshipType . '_') : '')
1447 . ($locationType ? ($locationType . '_') : '')
1448 . $key
1449 . ($entityLabel ? ('_' . $entityLabel) : '')
1450 );
1451 return $fieldKey;
1452 }
1453
1454 /**
1455 * Get params for the where criteria.
1456 *
1457 * @return mixed
1458 */
1459 public function getWhereParams() {
1460 if (!$this->isPostalableOnly()) {
1461 return [];
1462 }
1463 $params['is_deceased'] = ['is_deceased', '=', 0, CRM_Contact_BAO_Query::MODE_CONTACTS];
1464 $params['do_not_mail'] = ['do_not_mail', '=', 0, CRM_Contact_BAO_Query::MODE_CONTACTS];
1465 return $params;
1466 }
1467
1468 /**
1469 * @param $row
1470 * @param $contactID
1471 * @param $value
1472 * @param $field
1473 */
1474 protected function buildRelationshipFieldsForRow(&$row, $contactID, $value, $field) {
1475 foreach (array_keys($value) as $property) {
1476 if ($property === 'location') {
1477 // @todo just undo all this nasty location wrangling!
1478 foreach ($value['location'] as $locationKey => $locationFields) {
1479 foreach (array_keys($locationFields) as $locationField) {
1480 $fieldKey = str_replace(' ', '_', $locationKey . '-' . $locationField);
1481 $row[$field . '_' . $fieldKey] = $this->getRelationshipValue($field, $contactID, $fieldKey);
1482 }
1483 }
1484 }
1485 else {
1486 $row[$field . '_' . $property] = $this->getRelationshipValue($field, $contactID, $property);
1487 }
1488 }
1489 }
1490
1491 /**
1492 * Is this contact a household that is already set to be exported by virtue of it's household members.
1493 *
1494 * @param int $contactID
1495 *
1496 * @return bool
1497 */
1498 protected function isHouseholdToSkip($contactID) {
1499 return in_array($contactID, $this->householdsToSkip);
1500 }
1501
1502 /**
1503 * Get default return property for export based on mode
1504 *
1505 * @return string
1506 * Default Return property
1507 */
1508 public function defaultReturnProperty() {
1509 // hack to add default return property based on export mode
1510 $property = NULL;
1511 $exportMode = $this->getExportMode();
1512 if ($exportMode == CRM_Export_Form_Select::CONTRIBUTE_EXPORT) {
1513 $property = 'contribution_id';
1514 }
1515 elseif ($exportMode == CRM_Export_Form_Select::EVENT_EXPORT) {
1516 $property = 'participant_id';
1517 }
1518 elseif ($exportMode == CRM_Export_Form_Select::MEMBER_EXPORT) {
1519 $property = 'membership_id';
1520 }
1521 elseif ($exportMode == CRM_Export_Form_Select::PLEDGE_EXPORT) {
1522 $property = 'pledge_id';
1523 }
1524 elseif ($exportMode == CRM_Export_Form_Select::CASE_EXPORT) {
1525 $property = 'case_id';
1526 }
1527 elseif ($exportMode == CRM_Export_Form_Select::GRANT_EXPORT) {
1528 $property = 'grant_id';
1529 }
1530 elseif ($exportMode == CRM_Export_Form_Select::ACTIVITY_EXPORT) {
1531 $property = 'activity_id';
1532 }
1533 return $property;
1534 }
1535
1536 /**
1537 * Determine the required return properties from the input parameters.
1538 *
1539 * @return array
1540 */
1541 public function determineReturnProperties() {
1542 if ($this->getRequestedFields()) {
1543 $returnProperties = [];
1544 foreach ($this->getRequestedFields() as $key => $value) {
1545 $fieldName = CRM_Utils_Array::value(1, $value);
1546 if (!$fieldName || $this->isHouseholdMergeRelationshipTypeKey($fieldName)) {
1547 continue;
1548 }
1549
1550 if ($this->isRelationshipTypeKey($fieldName) && (!empty($value[2]) || !empty($value[4]))) {
1551 $returnProperties[$fieldName] = $this->setRelationshipReturnProperties($value, $fieldName);
1552 }
1553 elseif (is_numeric(CRM_Utils_Array::value(2, $value))) {
1554 $locationName = CRM_Core_PseudoConstant::getName('CRM_Core_BAO_Address', 'location_type_id', $value[2]);
1555 if ($fieldName == 'phone') {
1556 $returnProperties['location'][$locationName]['phone-' . CRM_Utils_Array::value(3, $value)] = 1;
1557 }
1558 elseif ($fieldName == 'im') {
1559 $returnProperties['location'][$locationName]['im-' . CRM_Utils_Array::value(3, $value)] = 1;
1560 }
1561 else {
1562 $returnProperties['location'][$locationName][$fieldName] = 1;
1563 }
1564 }
1565 else {
1566 //hack to fix component fields
1567 //revert mix of event_id and title
1568 if ($fieldName == 'event_id') {
1569 $returnProperties['event_id'] = 1;
1570 }
1571 else {
1572 $returnProperties[$fieldName] = 1;
1573 }
1574 }
1575 }
1576 $defaultExportMode = $this->defaultReturnProperty();
1577 if ($defaultExportMode) {
1578 $returnProperties[$defaultExportMode] = 1;
1579 }
1580 }
1581 else {
1582 $returnProperties = $this->getDefaultReturnProperties();
1583 }
1584 if ($this->isMergeSameHousehold()) {
1585 $returnProperties['id'] = 1;
1586 }
1587 if ($this->isMergeSameAddress()) {
1588 $returnProperties['addressee'] = 1;
1589 $returnProperties['postal_greeting'] = 1;
1590 $returnProperties['email_greeting'] = 1;
1591 $returnProperties['street_name'] = 1;
1592 $returnProperties['household_name'] = 1;
1593 $returnProperties['street_address'] = 1;
1594 $returnProperties['city'] = 1;
1595 $returnProperties['state_province'] = 1;
1596
1597 }
1598 return $returnProperties;
1599 }
1600
1601 }