Compress setting sql columns into output specification
[civicrm-core.git] / CRM / Export / BAO / ExportProcessor.php
CommitLineData
6003a964 1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
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-2018
32 */
33
34/**
35 * Class CRM_Export_BAO_ExportProcessor
36 *
37 * Class to handle logic of export.
38 */
39class CRM_Export_BAO_ExportProcessor {
40
41 /**
42 * @var int
43 */
44 protected $queryMode;
45
46 /**
47 * @var int
48 */
49 protected $exportMode;
50
adabfa40 51 /**
52 * Array of fields in the main query.
53 *
54 * @var array
55 */
56 protected $queryFields = [];
57
71464b73 58 /**
59 * Either AND or OR.
60 *
61 * @var string
62 */
63 protected $queryOperator;
64
d41ab886 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
b7db6051 75 /**
76 * Is the contact being merged into a single household.
77 *
78 * @var bool
79 */
80 protected $isMergeSameHousehold;
81
944ed388 82 /**
83 * Key representing the head of household in the relationship array.
84 *
85 * e.g. ['8_b_a' => 'Household Member Is', '8_a_b = 'Household Member Of'.....]
86 *
87 * @var
88 */
89 protected $relationshipTypes = [];
90
704e3e9a 91 /**
92 * Array of properties to retrieve for relationships.
93 *
94 * @var array
95 */
96 protected $relationshipReturnProperties = [];
97
ce12a9e0 98 /**
99 * Get return properties by relationship.
100 * @return array
101 */
102 public function getRelationshipReturnProperties() {
103 return $this->relationshipReturnProperties;
104 }
105
106 /**
107 * Export values for related contacts.
108 *
109 * @var array
110 */
111 protected $relatedContactValues = [];
112
c66a5741 113 /**
114 * @var array
115 */
116 protected $returnProperties = [];
117
28254dcb 118 /**
119 * @var array
120 */
121 protected $outputSpecification = [];
122
71464b73 123 /**
124 * CRM_Export_BAO_ExportProcessor constructor.
125 *
126 * @param int $exportMode
d41ab886 127 * @param array|NULL $requestedFields
71464b73 128 * @param string $queryOperator
b7db6051 129 * @param bool $isMergeSameHousehold
71464b73 130 */
b7db6051 131 public function __construct($exportMode, $requestedFields, $queryOperator, $isMergeSameHousehold = FALSE) {
71464b73 132 $this->setExportMode($exportMode);
133 $this->setQueryMode();
134 $this->setQueryOperator($queryOperator);
d41ab886 135 $this->setRequestedFields($requestedFields);
944ed388 136 $this->setRelationshipTypes();
b7db6051 137 $this->setIsMergeSameHousehold($isMergeSameHousehold);
d41ab886 138 }
139
140 /**
141 * @return array|null
142 */
143 public function getRequestedFields() {
144 return $this->requestedFields;
145 }
146
147 /**
148 * @param array|null $requestedFields
149 */
150 public function setRequestedFields($requestedFields) {
151 $this->requestedFields = $requestedFields;
71464b73 152 }
153
c66a5741 154 /**
155 * @return array
156 */
157 public function getReturnProperties() {
158 return $this->returnProperties;
159 }
160
161 /**
162 * @param array $returnProperties
163 */
164 public function setReturnProperties($returnProperties) {
165 $this->returnProperties = $returnProperties;
166 }
167
944ed388 168 /**
169 * @return array
170 */
171 public function getRelationshipTypes() {
172 return $this->relationshipTypes;
173 }
174
175 /**
176 */
177 public function setRelationshipTypes() {
178 $this->relationshipTypes = CRM_Contact_BAO_Relationship::getContactRelationshipType(
179 NULL,
180 NULL,
181 NULL,
182 NULL,
183 TRUE,
184 'name',
185 FALSE
186 );
187 }
188
ce12a9e0 189 /**
190 * Set the value for a relationship type field.
191 *
192 * In this case we are building up an array of properties for a related contact.
193 *
194 * These may be used for direct exporting or for merge to household depending on the
195 * options selected.
196 *
197 * @param string $relationshipType
198 * @param int $contactID
199 * @param string $field
200 * @param string $value
201 */
202 public function setRelationshipValue($relationshipType, $contactID, $field, $value) {
203 $this->relatedContactValues[$relationshipType][$contactID][$field] = $value;
204 }
205
206 /**
207 * Get the value for a relationship type field.
208 *
209 * In this case we are building up an array of properties for a related contact.
210 *
211 * These may be used for direct exporting or for merge to household depending on the
212 * options selected.
213 *
214 * @param string $relationshipType
215 * @param int $contactID
216 * @param string $field
217 *
218 * @return string
219 */
220 public function getRelationshipValue($relationshipType, $contactID, $field) {
221 return isset($this->relatedContactValues[$relationshipType][$contactID][$field]) ? $this->relatedContactValues[$relationshipType][$contactID][$field] : '';
222 }
223
b7db6051 224 /**
225 * @return bool
226 */
227 public function isMergeSameHousehold() {
228 return $this->isMergeSameHousehold;
229 }
230
231 /**
232 * @param bool $isMergeSameHousehold
233 */
234 public function setIsMergeSameHousehold($isMergeSameHousehold) {
235 $this->isMergeSameHousehold = $isMergeSameHousehold;
236 }
237
238 /**
239 * Return relationship types for household merge.
240 *
241 * @return mixed
242 */
243 public function getHouseholdRelationshipTypes() {
244 if (!$this->isMergeSameHousehold()) {
245 return [];
246 }
247 return [
248 CRM_Utils_Array::key('Household Member of', $this->getRelationshipTypes()),
249 CRM_Utils_Array::key('Head of Household for', $this->getRelationshipTypes()),
250 ];
251 }
944ed388 252
253 /**
254 * @param $fieldName
255 * @return bool
256 */
257 public function isRelationshipTypeKey($fieldName) {
258 return array_key_exists($fieldName, $this->relationshipTypes);
259 }
260
b7db6051 261
262 /**
263 * @param $fieldName
264 * @return bool
265 */
266 public function isHouseholdMergeRelationshipTypeKey($fieldName) {
267 return in_array($fieldName, $this->getHouseholdRelationshipTypes());
268 }
269
71464b73 270 /**
271 * @return string
272 */
273 public function getQueryOperator() {
274 return $this->queryOperator;
275 }
276
277 /**
278 * @param string $queryOperator
279 */
280 public function setQueryOperator($queryOperator) {
281 $this->queryOperator = $queryOperator;
282 }
283
adabfa40 284 /**
285 * @return array
286 */
287 public function getQueryFields() {
288 return $this->queryFields;
289 }
290
291 /**
292 * @param array $queryFields
293 */
294 public function setQueryFields($queryFields) {
5ebd7d09 295 // legacy hacks - we add these to queryFields because this
296 // pseudometadata is currently required.
297 $queryFields['im_provider']['pseudoconstant']['var'] = 'imProviders';
298 $queryFields['country']['context'] = 'country';
299 $queryFields['world_region']['context'] = 'country';
300 $queryFields['state_province']['context'] = 'province';
adabfa40 301 $this->queryFields = $queryFields;
302 }
303
6003a964 304 /**
305 * @return int
306 */
307 public function getQueryMode() {
308 return $this->queryMode;
309 }
310
311 /**
312 * Set the query mode based on the export mode.
313 */
314 public function setQueryMode() {
315
316 switch ($this->getExportMode()) {
317 case CRM_Export_Form_Select::CONTRIBUTE_EXPORT:
318 $this->queryMode = CRM_Contact_BAO_Query::MODE_CONTRIBUTE;
319 break;
320
321 case CRM_Export_Form_Select::EVENT_EXPORT:
322 $this->queryMode = CRM_Contact_BAO_Query::MODE_EVENT;
323 break;
324
325 case CRM_Export_Form_Select::MEMBER_EXPORT:
326 $this->queryMode = CRM_Contact_BAO_Query::MODE_MEMBER;
327 break;
328
329 case CRM_Export_Form_Select::PLEDGE_EXPORT:
330 $this->queryMode = CRM_Contact_BAO_Query::MODE_PLEDGE;
331 break;
332
333 case CRM_Export_Form_Select::CASE_EXPORT:
334 $this->queryMode = CRM_Contact_BAO_Query::MODE_CASE;
335 break;
336
337 case CRM_Export_Form_Select::GRANT_EXPORT:
338 $this->queryMode = CRM_Contact_BAO_Query::MODE_GRANT;
339 break;
340
341 case CRM_Export_Form_Select::ACTIVITY_EXPORT:
342 $this->queryMode = CRM_Contact_BAO_Query::MODE_ACTIVITY;
343 break;
344
345 default:
346 $this->queryMode = CRM_Contact_BAO_Query::MODE_CONTACTS;
347 }
348 }
349
350 /**
351 * @return int
352 */
353 public function getExportMode() {
354 return $this->exportMode;
355 }
356
357 /**
358 * @param int $exportMode
359 */
360 public function setExportMode($exportMode) {
361 $this->exportMode = $exportMode;
362 }
363
29034a98 364 /**
365 * Get the name for the export file.
366 *
367 * @return string
368 */
369 public function getExportFileName() {
370 switch ($this->getExportMode()) {
371 case CRM_Export_Form_Select::CONTACT_EXPORT:
372 return ts('CiviCRM Contact Search');
373
374 case CRM_Export_Form_Select::CONTRIBUTE_EXPORT:
375 return ts('CiviCRM Contribution Search');
376
377 case CRM_Export_Form_Select::MEMBER_EXPORT:
378 return ts('CiviCRM Member Search');
379
380 case CRM_Export_Form_Select::EVENT_EXPORT:
381 return ts('CiviCRM Participant Search');
382
383 case CRM_Export_Form_Select::PLEDGE_EXPORT:
384 return ts('CiviCRM Pledge Search');
385
386 case CRM_Export_Form_Select::CASE_EXPORT:
387 return ts('CiviCRM Case Search');
388
389 case CRM_Export_Form_Select::GRANT_EXPORT:
390 return ts('CiviCRM Grant Search');
391
392 case CRM_Export_Form_Select::ACTIVITY_EXPORT:
393 return ts('CiviCRM Activity Search');
394
395 default:
396 // Legacy code suggests the value could be 'financial' - ie. something
397 // other than what should be accepted. However, I suspect that this line is
398 // never hit.
399 return ts('CiviCRM Search');
400 }
401 }
402
af17bedf 403 /**
404 * Get the label for the header row based on the field to output.
405 *
406 * @param string $field
407 *
408 * @return string
409 */
410 public function getHeaderForRow($field) {
411 if (substr($field, -11) == 'campaign_id') {
412 // @todo - set this correctly in the xml rather than here.
413 // This will require a generalised handling cleanup
414 return ts('Campaign ID');
415 }
416 if ($this->isMergeSameHousehold() && $field === 'id') {
417 return ts('Household ID');
418 }
419 elseif (isset($this->getQueryFields()[$field]['title'])) {
420 return $this->getQueryFields()[$field]['title'];
421 }
422 elseif ($this->isExportPaymentFields() && array_key_exists($field, $this->getcomponentPaymentFields())) {
423 return CRM_Utils_Array::value($field, $this->getcomponentPaymentFields());
424 }
425 else {
426 return $field;
427 }
428 }
429
adabfa40 430 /**
431 * @param $params
432 * @param $order
adabfa40 433 * @param $returnProperties
434 * @return array
435 */
71464b73 436 public function runQuery($params, $order, $returnProperties) {
adabfa40 437 $query = new CRM_Contact_BAO_Query($params, $returnProperties, NULL,
438 FALSE, FALSE, $this->getQueryMode(),
71464b73 439 FALSE, TRUE, TRUE, NULL, $this->getQueryOperator()
adabfa40 440 );
441
442 //sort by state
443 //CRM-15301
444 $query->_sort = $order;
445 list($select, $from, $where, $having) = $query->query();
446 $this->setQueryFields($query->_fields);
447 return array($query, $select, $from, $where, $having);
448 }
449
28254dcb 450 /**
451 * Add a row to the specification for how to output data.
ae5d4caf 452 *
28254dcb 453 * @param string $key
28254dcb 454 * @param string $relationshipType
ae5d4caf 455 * @param string $locationType
456 * @param int $entityTypeID phone_type_id or provider_id for phone or im fields.
28254dcb 457 */
ae5d4caf 458 public function addOutputSpecification($key, $relationshipType = NULL, $locationType = NULL, $entityTypeID = NULL) {
5ebd7d09 459 $entityLabel = '';
ae5d4caf 460 if ($entityTypeID) {
461 if ($key === 'phone') {
5ebd7d09 462 $entityLabel = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_Phone', 'phone_type_id', $entityTypeID);
ae5d4caf 463 }
464 if ($key === 'im') {
5ebd7d09 465 $entityLabel = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_IM', 'provider_id', $entityTypeID);
ae5d4caf 466 }
467 }
5ebd7d09 468
469 // These oddly constructed keys are for legacy reasons. Altering them will affect test success
470 // but in time it may be good to rationalise them.
471 $label = $this->getOutputSpecificationLabel($key, $relationshipType, $locationType, $entityLabel);
472 $index = $this->getOutputSpecificationIndex($key, $relationshipType, $locationType, $entityLabel);
473 $fieldKey = $this->getOutputSpecificationFieldKey($key, $relationshipType, $locationType, $entityLabel);
474
475 $this->outputSpecification[$index]['header'] = $label;
476 $this->outputSpecification[$index]['sql_columns'] = $this->getSqlColumnDefinition($fieldKey, $key);
477
478 if ($relationshipType && $this->isHouseholdMergeRelationshipTypeKey($relationshipType)) {
479 $this->setColumnAsCalculationOnly($index);
480 }
481 $this->outputSpecification[$index]['metadata'] = $this->getMetaDataForField($key);
482 }
483
484 /**
485 * Get the metadata for the given field.
486 *
487 * @param $key
488 *
489 * @return array
490 */
491 public function getMetaDataForField($key) {
492 $mappings = ['contact_id' => 'id'];
493 if (isset($this->getQueryFields()[$key])) {
494 return $this->getQueryFields()[$key];
495 }
496 if (isset($mappings[$key])) {
497 return $this->getQueryFields()[$mappings[$key]];
498 }
499 return [];
2cd3d767 500 }
ae5d4caf 501
2cd3d767 502 /**
503 * @param $key
504 */
505 public function setSqlColumnDefn($key) {
5ebd7d09 506 $this->outputSpecification[$this->getMungedFieldName($key)]['sql_columns'] = $this->getSqlColumnDefinition($key, $this->getMungedFieldName($key));
de6ff509 507 }
508
509 /**
510 * Mark a column as only required for calculations.
511 *
512 * Do not include the row with headers.
513 *
514 * @param string $column
515 */
516 public function setColumnAsCalculationOnly($column) {
517 $this->outputSpecification[$column]['do_not_output_to_csv'] = TRUE;
28254dcb 518 }
519
520 /**
521 * @return array
522 */
523 public function getHeaderRows() {
524 $headerRows = [];
525 foreach ($this->outputSpecification as $key => $spec) {
de6ff509 526 if (empty($spec['do_not_output_to_csv'])) {
527 $headerRows[] = $spec['header'];
528 }
28254dcb 529 }
530 return $headerRows;
531 }
532
2cd3d767 533 /**
534 * @return array
535 */
536 public function getSQLColumns() {
537 $sqlColumns = [];
538 foreach ($this->outputSpecification as $key => $spec) {
539 if (empty($spec['do_not_output_to_sql'])) {
540 $sqlColumns[$key] = $spec['sql_columns'];
541 }
542 }
543 return $sqlColumns;
544 }
545
5ebd7d09 546 /**
547 * @return array
548 */
549 public function getMetadata() {
550 $metadata = [];
551 foreach ($this->outputSpecification as $key => $spec) {
552 $metadata[$key] = $spec['metadata'];
553 }
554 return $metadata;
555 }
2cd3d767 556
6e2de55d 557 /**
558 * Build the row for output.
559 *
560 * @param \CRM_Contact_BAO_Query $query
561 * @param CRM_Core_DAO $iterationDAO
562 * @param array $outputColumns
563 * @param $metadata
564 * @param $paymentDetails
565 * @param $addPaymentHeader
566 * @param $paymentTableId
567 *
568 * @return array
569 */
570 public function buildRow($query, $iterationDAO, $outputColumns, $metadata, $paymentDetails, $addPaymentHeader, $paymentTableId) {
571 $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id');
572 $imProviders = CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id');
573
574 $row = [];
575 $query->convertToPseudoNames($iterationDAO);
576
577 //first loop through output columns so that we return what is required, and in same order.
578 foreach ($outputColumns as $field => $value) {
579
580 // add im_provider to $dao object
581 if ($field == 'im_provider' && property_exists($iterationDAO, 'provider_id')) {
582 $iterationDAO->im_provider = $iterationDAO->provider_id;
583 }
584
585 //build row values (data)
586 $fieldValue = NULL;
587 if (property_exists($iterationDAO, $field)) {
588 $fieldValue = $iterationDAO->$field;
589 // to get phone type from phone type id
590 if ($field == 'phone_type_id' && isset($phoneTypes[$fieldValue])) {
591 $fieldValue = $phoneTypes[$fieldValue];
592 }
593 elseif ($field == 'provider_id' || $field == 'im_provider') {
594 $fieldValue = CRM_Utils_Array::value($fieldValue, $imProviders);
595 }
596 elseif (strstr($field, 'master_id')) {
597 $masterAddressId = NULL;
598 if (isset($iterationDAO->$field)) {
599 $masterAddressId = $iterationDAO->$field;
600 }
601 // get display name of contact that address is shared.
602 $fieldValue = CRM_Contact_BAO_Contact::getMasterDisplayName($masterAddressId);
603 }
604 }
605
606 if ($this->isRelationshipTypeKey($field)) {
607 foreach (array_keys($value) as $property) {
608 if ($property === 'location') {
609 // @todo just undo all this nasty location wrangling!
610 foreach ($value['location'] as $locationKey => $locationFields) {
611 foreach (array_keys($locationFields) as $locationField) {
612 $fieldKey = str_replace(' ', '_', $locationKey . '-' . $locationField);
613 $row[$field . '_' . $fieldKey] = $this->getRelationshipValue($field, $iterationDAO->contact_id, $fieldKey);
614 }
615 }
616 }
617 else {
618 $row[$field . '_' . $property] = $this->getRelationshipValue($field, $iterationDAO->contact_id, $property);
619 }
620 }
621 }
622 else {
623 $row[$field] = $this->getTransformedFieldValue($field, $iterationDAO, $fieldValue, $metadata, $paymentDetails);
624 }
625 }
626
627 // If specific payment fields have been selected for export, payment
628 // data will already be in $row. Otherwise, add payment related
629 // information, if appropriate.
630 if ($addPaymentHeader) {
631 if (!$this->isExportSpecifiedPaymentFields()) {
632 $nullContributionDetails = array_fill_keys(array_keys($this->getPaymentHeaders()), NULL);
633 if ($this->isExportPaymentFields()) {
634 $paymentData = CRM_Utils_Array::value($row[$paymentTableId], $paymentDetails);
635 if (!is_array($paymentData) || empty($paymentData)) {
636 $paymentData = $nullContributionDetails;
637 }
638 $row = array_merge($row, $paymentData);
639 }
640 elseif (!empty($paymentDetails)) {
641 $row = array_merge($row, $nullContributionDetails);
642 }
643 }
644 }
645 //remove organization name for individuals if it is set for current employer
646 if (!empty($row['contact_type']) &&
647 $row['contact_type'] == 'Individual' && array_key_exists('organization_name', $row)
648 ) {
649 $row['organization_name'] = '';
650 }
651 return $row;
652 }
653
8bd67f47 654 /**
655 * @param $field
656 * @param $iterationDAO
657 * @param $fieldValue
658 * @param $metadata
659 * @param $paymentDetails
660 *
661 * @return string
662 */
663 public function getTransformedFieldValue($field, $iterationDAO, $fieldValue, $metadata, $paymentDetails) {
664
665 $i18n = CRM_Core_I18n::singleton();
666 if ($field == 'id') {
667 return $iterationDAO->contact_id;
668 // special case for calculated field
669 }
670 elseif ($field == 'source_contact_id') {
671 return $iterationDAO->contact_id;
672 }
673 elseif ($field == 'pledge_balance_amount') {
674 return $iterationDAO->pledge_amount - $iterationDAO->pledge_total_paid;
675 // special case for calculated field
676 }
677 elseif ($field == 'pledge_next_pay_amount') {
678 return $iterationDAO->pledge_next_pay_amount + $iterationDAO->pledge_outstanding_amount;
679 }
680 elseif (isset($fieldValue) &&
681 $fieldValue != ''
682 ) {
683 //check for custom data
684 if ($cfID = CRM_Core_BAO_CustomField::getKeyID($field)) {
685 return CRM_Core_BAO_CustomField::displayValue($fieldValue, $cfID);
686 }
687
688 elseif (in_array($field, array(
689 'email_greeting',
690 'postal_greeting',
691 'addressee',
692 ))) {
693 //special case for greeting replacement
694 $fldValue = "{$field}_display";
695 return $iterationDAO->$fldValue;
696 }
697 else {
698 //normal fields with a touch of CRM-3157
699 switch ($field) {
700 case 'country':
701 case 'world_region':
702 return $i18n->crm_translate($fieldValue, array('context' => 'country'));
703
704 case 'state_province':
705 return $i18n->crm_translate($fieldValue, array('context' => 'province'));
706
707 case 'gender':
708 case 'preferred_communication_method':
709 case 'preferred_mail_format':
710 case 'communication_style':
711 return $i18n->crm_translate($fieldValue);
712
713 default:
714 if (isset($metadata[$field])) {
715 // No I don't know why we do it this way & whether we could
716 // make better use of pseudoConstants.
717 if (!empty($metadata[$field]['context'])) {
718 return $i18n->crm_translate($fieldValue, $metadata[$field]);
719 }
720 if (!empty($metadata[$field]['pseudoconstant'])) {
721 // This is not our normal syntax for pseudoconstants but I am a bit loath to
722 // call an external function until sure it is not increasing php processing given this
723 // may be iterated 100,000 times & we already have the $imProvider var loaded.
724 // That can be next refactor...
725 // Yes - definitely feeling hatred for this bit of code - I know you will beat me up over it's awfulness
726 // but I have to reach a stable point....
727 $varName = $metadata[$field]['pseudoconstant']['var'];
728 if ($varName === 'imProviders') {
729 return CRM_Core_PseudoConstant::getLabel('CRM_Core_DAO_IM', 'provider_id', $fieldValue);
730 }
731 if ($varName === 'phoneTypes') {
732 return CRM_Core_PseudoConstant::getLabel('CRM_Core_DAO_Phone', 'phone_type_id', $fieldValue);
733 }
734 }
735
736 }
737 return $fieldValue;
738 }
739 }
740 }
741 elseif ($this->isExportSpecifiedPaymentFields() && array_key_exists($field, $this->getcomponentPaymentFields())) {
742 $paymentTableId = $this->getPaymentTableID();
743 $paymentData = CRM_Utils_Array::value($iterationDAO->$paymentTableId, $paymentDetails);
744 $payFieldMapper = array(
745 'componentPaymentField_total_amount' => 'total_amount',
746 'componentPaymentField_contribution_status' => 'contribution_status',
747 'componentPaymentField_payment_instrument' => 'pay_instru',
748 'componentPaymentField_transaction_id' => 'trxn_id',
749 'componentPaymentField_received_date' => 'receive_date',
750 );
751 return CRM_Utils_Array::value($payFieldMapper[$field], $paymentData, '');
752 }
753 else {
754 // if field is empty or null
755 return '';
756 }
757 }
758
ce14544c 759 /**
760 * Get array of fields to return, over & above those defined in the main contact exportable fields.
761 *
762 * These include export mode specific fields & some fields apparently required as 'exportableFields'
763 * but not returned by the function of the same name.
764 *
765 * @return array
766 * Array of fields to return in the format ['field_name' => 1,...]
767 */
768 public function getAdditionalReturnProperties() {
ce14544c 769 if ($this->getQueryMode() === CRM_Contact_BAO_Query::MODE_CONTACTS) {
770 $componentSpecificFields = [];
771 }
772 else {
773 $componentSpecificFields = CRM_Contact_BAO_Query::defaultReturnProperties($this->getQueryMode());
774 }
775 if ($this->getQueryMode() === CRM_Contact_BAO_Query::MODE_PLEDGE) {
776 $componentSpecificFields = array_merge($componentSpecificFields, CRM_Pledge_BAO_Query::extraReturnProperties($this->getQueryMode()));
d28b6cf2 777 unset($componentSpecificFields['contribution_status_id']);
778 unset($componentSpecificFields['pledge_status_id']);
779 unset($componentSpecificFields['pledge_payment_status_id']);
ce14544c 780 }
781 if ($this->getQueryMode() === CRM_Contact_BAO_Query::MODE_CASE) {
782 $componentSpecificFields = array_merge($componentSpecificFields, CRM_Case_BAO_Query::extraReturnProperties($this->getQueryMode()));
783 }
784 if ($this->getQueryMode() === CRM_Contact_BAO_Query::MODE_CONTRIBUTE) {
785 $componentSpecificFields = array_merge($componentSpecificFields, CRM_Contribute_BAO_Query::softCreditReturnProperties(TRUE));
d28b6cf2 786 unset($componentSpecificFields['contribution_status_id']);
ce14544c 787 }
7626754f 788 return $componentSpecificFields;
ce14544c 789 }
790
d41ab886 791 /**
792 * Should payment fields be appended to the export.
793 *
794 * (This is pretty hacky so hopefully this function won't last long - notice
795 * how obviously it should be part of the above function!).
796 */
797 public function isExportPaymentFields() {
798 if ($this->getRequestedFields() === NULL
f065c170 799 && in_array($this->getQueryMode(), [
d41ab886 800 CRM_Contact_BAO_Query::MODE_EVENT,
801 CRM_Contact_BAO_Query::MODE_MEMBER,
802 CRM_Contact_BAO_Query::MODE_PLEDGE,
803 ])) {
804 return TRUE;
805 }
c66a5741 806 elseif ($this->isExportSpecifiedPaymentFields()) {
807 return TRUE;
808 }
d41ab886 809 return FALSE;
810 }
811
c66a5741 812 /**
813 * Has specific payment fields been requested (as opposed to via all fields).
814 *
815 * If specific fields have been requested then they get added at various points.
816 *
817 * @return bool
818 */
819 public function isExportSpecifiedPaymentFields() {
820 if ($this->getRequestedFields() !== NULL && $this->hasRequestedComponentPaymentFields()) {
821 return TRUE;
822 }
823 }
824
d41ab886 825 /**
826 * Get the name of the id field in the table that connects contributions to the export entity.
827 */
828 public function getPaymentTableID() {
829 if ($this->getRequestedFields() === NULL) {
830 $mapping = [
831 CRM_Contact_BAO_Query::MODE_EVENT => 'participant_id',
832 CRM_Contact_BAO_Query::MODE_MEMBER => 'membership_id',
833 CRM_Contact_BAO_Query::MODE_PLEDGE => 'pledge_payment_id',
834 ];
835 return isset($mapping[$this->getQueryMode()]) ? $mapping[$this->getQueryMode()] : '';
836 }
c66a5741 837 elseif ($this->hasRequestedComponentPaymentFields()) {
838 return 'participant_id';
839 }
d41ab886 840 return FALSE;
841 }
842
c66a5741 843 /**
844 * Have component payment fields been requested.
845 *
846 * @return bool
847 */
848 protected function hasRequestedComponentPaymentFields() {
849 if ($this->getQueryMode() === CRM_Contact_BAO_Query::MODE_EVENT) {
850 $participantPaymentFields = array_intersect_key($this->getComponentPaymentFields(), $this->getReturnProperties());
851 if (!empty($participantPaymentFields)) {
852 return TRUE;
853 }
854 }
d41ab886 855 return FALSE;
856 }
857
c66a5741 858 /**
859 * Get fields that indicate payment fields have been requested for a component.
860 *
0e32ed68 861 * Ideally this should be protected but making it temporarily public helps refactoring..
862 *
c66a5741 863 * @return array
864 */
0e32ed68 865 public function getComponentPaymentFields() {
c66a5741 866 return [
867 'componentPaymentField_total_amount' => ts('Total Amount'),
868 'componentPaymentField_contribution_status' => ts('Contribution Status'),
869 'componentPaymentField_received_date' => ts('Date Received'),
870 'componentPaymentField_payment_instrument' => ts('Payment Method'),
871 'componentPaymentField_transaction_id' => ts('Transaction ID'),
872 ];
873 }
874
05ad310f 875 /**
876 * Get headers for payment fields.
877 *
878 * Returns an array of contribution fields when the entity supports payment fields and specific fields
879 * are not specified. This is a transitional function for refactoring legacy code.
880 */
881 public function getPaymentHeaders() {
882 if ($this->isExportPaymentFields() && !$this->isExportSpecifiedPaymentFields()) {
883 return $this->getcomponentPaymentFields();
884 }
885 return [];
886 }
887
d41ab886 888 /**
889 * Get the default properties when not specified.
890 *
891 * In the UI this appears as 'Primary fields only' but in practice it's
892 * most of the kitchen sink and the hallway closet thrown in.
893 *
894 * Since CRM-952 custom fields are excluded, but no other form of mercy is shown.
895 *
896 * @return array
897 */
898 public function getDefaultReturnProperties() {
899 $returnProperties = [];
900 $fields = CRM_Contact_BAO_Contact::exportableFields('All', TRUE, TRUE);
704e3e9a 901 $skippedFields = ($this->getQueryMode() === CRM_Contact_BAO_Query::MODE_CONTACTS) ? [] : [
902 'groups',
903 'tags',
904 'notes'
905 ];
d41ab886 906
907 foreach ($fields as $key => $var) {
908 if ($key && (substr($key, 0, 6) != 'custom') && !in_array($key, $skippedFields)) {
909 $returnProperties[$key] = 1;
910 }
911 }
912 $returnProperties = array_merge($returnProperties, $this->getAdditionalReturnProperties());
913 return $returnProperties;
914 }
915
704e3e9a 916 /**
917 * Add the field to relationship return properties & return it.
918 *
919 * This function is doing both setting & getting which is yuck but it is an interim
920 * refactor.
921 *
922 * @param array $value
923 * @param string $relationshipKey
924 *
925 * @return array
926 */
927 public function setRelationshipReturnProperties($value, $relationshipKey) {
704e3e9a 928 $relPhoneTypeId = $relIMProviderId = NULL;
929 if (!empty($value[2])) {
930 $relationField = CRM_Utils_Array::value(2, $value);
931 if (trim(CRM_Utils_Array::value(3, $value))) {
932 $relLocTypeId = CRM_Utils_Array::value(3, $value);
933 }
934 else {
935 $relLocTypeId = 'Primary';
936 }
937
938 if ($relationField == 'phone') {
939 $relPhoneTypeId = CRM_Utils_Array::value(4, $value);
940 }
941 elseif ($relationField == 'im') {
942 $relIMProviderId = CRM_Utils_Array::value(4, $value);
943 }
944 }
945 elseif (!empty($value[4])) {
946 $relationField = CRM_Utils_Array::value(4, $value);
947 $relLocTypeId = CRM_Utils_Array::value(5, $value);
948 if ($relationField == 'phone') {
949 $relPhoneTypeId = CRM_Utils_Array::value(6, $value);
950 }
951 elseif ($relationField == 'im') {
952 $relIMProviderId = CRM_Utils_Array::value(6, $value);
953 }
954 }
955 if (in_array($relationField, $this->getValidLocationFields()) && is_numeric($relLocTypeId)) {
a16a432a 956 $locationName = CRM_Core_PseudoConstant::getName('CRM_Core_BAO_Address', 'location_type_id', $relLocTypeId);
704e3e9a 957 if ($relPhoneTypeId) {
a16a432a 958 $this->relationshipReturnProperties[$relationshipKey]['location'][$locationName]['phone-' . $relPhoneTypeId] = 1;
704e3e9a 959 }
960 elseif ($relIMProviderId) {
a16a432a 961 $this->relationshipReturnProperties[$relationshipKey]['location'][$locationName]['im-' . $relIMProviderId] = 1;
704e3e9a 962 }
963 else {
a16a432a 964 $this->relationshipReturnProperties[$relationshipKey]['location'][$locationName][$relationField] = 1;
704e3e9a 965 }
966 }
967 else {
968 $this->relationshipReturnProperties[$relationshipKey][$relationField] = 1;
969 }
970 return $this->relationshipReturnProperties[$relationshipKey];
971 }
972
ce12a9e0 973 /**
974 * Add the main return properties to the household merge properties if needed for merging.
975 *
976 * If we are using household merge we need to add these to the relationship properties to
977 * be retrieved.
978 *
979 * @param $returnProperties
980 */
981 public function setHouseholdMergeReturnProperties($returnProperties) {
982 foreach ($this->getHouseholdRelationshipTypes() as $householdRelationshipType) {
983 $this->relationshipReturnProperties[$householdRelationshipType] = $returnProperties;
984 }
985 }
986
704e3e9a 987 /**
988 * Get the default location fields to request.
989 *
990 * @return array
991 */
992 public function getValidLocationFields() {
993 return [
994 'street_address',
995 'supplemental_address_1',
996 'supplemental_address_2',
997 'supplemental_address_3',
998 'city',
999 'postal_code',
1000 'postal_code_suffix',
1001 'geo_code_1',
1002 'geo_code_2',
1003 'state_province',
1004 'country',
1005 'phone',
1006 'email',
1007 'im',
1008 ];
1009 }
1010
aa3a113b 1011 /**
c8adad81 1012 * Get the sql column definition for the given field.
1013 *
5ebd7d09 1014 * @param string $fieldName
1015 * @param string $columnName
aa3a113b 1016 *
1017 * @return mixed
1018 */
5ebd7d09 1019 public function getSqlColumnDefinition($fieldName, $columnName) {
aa3a113b 1020
1021 // early exit for master_id, CRM-12100
1022 // in the DB it is an ID, but in the export, we retrive the display_name of the master record
1023 // also for current_employer, CRM-16939
5ebd7d09 1024 if ($columnName == 'master_id' || $columnName == 'current_employer') {
aa3a113b 1025 return "$fieldName varchar(128)";
1026 }
1027
1028 if (substr($fieldName, -11) == 'campaign_id') {
1029 // CRM-14398
1030 return "$fieldName varchar(128)";
1031 }
1032
1033 $queryFields = $this->getQueryFields();
1034 $lookUp = ['prefix_id', 'suffix_id'];
1035 // set the sql columns
5ebd7d09 1036 if (isset($queryFields[$columnName]['type'])) {
1037 switch ($queryFields[$columnName]['type']) {
aa3a113b 1038 case CRM_Utils_Type::T_INT:
1039 case CRM_Utils_Type::T_BOOLEAN:
5ebd7d09 1040 if (in_array($columnName, $lookUp)) {
aa3a113b 1041 return "$fieldName varchar(255)";
1042 }
1043 else {
1044 return "$fieldName varchar(16)";
1045 }
1046
1047 case CRM_Utils_Type::T_STRING:
5ebd7d09 1048 if (isset($queryFields[$columnName]['maxlength'])) {
1049 return "$fieldName varchar({$queryFields[$columnName]['maxlength']})";
aa3a113b 1050 }
1051 else {
1052 return "$fieldName varchar(255)";
1053 }
1054
1055 case CRM_Utils_Type::T_TEXT:
1056 case CRM_Utils_Type::T_LONGTEXT:
1057 case CRM_Utils_Type::T_BLOB:
1058 case CRM_Utils_Type::T_MEDIUMBLOB:
1059 return "$fieldName longtext";
1060
1061 case CRM_Utils_Type::T_FLOAT:
1062 case CRM_Utils_Type::T_ENUM:
1063 case CRM_Utils_Type::T_DATE:
1064 case CRM_Utils_Type::T_TIME:
1065 case CRM_Utils_Type::T_TIMESTAMP:
1066 case CRM_Utils_Type::T_MONEY:
1067 case CRM_Utils_Type::T_EMAIL:
1068 case CRM_Utils_Type::T_URL:
1069 case CRM_Utils_Type::T_CCNUM:
1070 default:
1071 return "$fieldName varchar(32)";
1072 }
1073 }
1074 else {
1075 if (substr($fieldName, -3, 3) == '_id') {
1076 return "$fieldName varchar(255)";
1077 }
1078 elseif (substr($fieldName, -5, 5) == '_note') {
1079 return "$fieldName text";
1080 }
1081 else {
1082 $changeFields = [
1083 'groups',
1084 'tags',
1085 'notes',
1086 ];
1087
1088 if (in_array($fieldName, $changeFields)) {
1089 return "$fieldName text";
1090 }
1091 else {
1092 // set the sql columns for custom data
5ebd7d09 1093 if (isset($queryFields[$columnName]['data_type'])) {
aa3a113b 1094
5ebd7d09 1095 switch ($queryFields[$columnName]['data_type']) {
aa3a113b 1096 case 'String':
1097 // May be option labels, which could be up to 512 characters
5ebd7d09 1098 $length = max(512, CRM_Utils_Array::value('text_length', $queryFields[$columnName]));
aa3a113b 1099 return "$fieldName varchar($length)";
1100
1101 case 'Country':
1102 case 'StateProvince':
1103 case 'Link':
1104 return "$fieldName varchar(255)";
1105
1106 case 'Memo':
1107 return "$fieldName text";
1108
1109 default:
1110 return "$fieldName varchar(255)";
1111 }
1112 }
1113 else {
1114 return "$fieldName text";
1115 }
1116 }
1117 }
1118 }
1119 }
1120
c8adad81 1121 /**
1122 * Get the munged field name.
1123 *
1124 * @param string $field
1125 * @return string
1126 */
1127 public function getMungedFieldName($field) {
1128 $fieldName = CRM_Utils_String::munge(strtolower($field), '_', 64);
1129 if ($fieldName == 'id') {
1130 $fieldName = 'civicrm_primary_id';
1131 }
1132 return $fieldName;
1133 }
1134
5ebd7d09 1135 /**
1136 * In order to respect the history of this class we need to index kinda illogically.
1137 *
1138 * On the bright side - this stuff is tested within a nano-byte of it's life.
1139 *
1140 * e.g '2-a-b_Home-City'
1141 *
1142 * @param string $key
1143 * @param string $relationshipType
1144 * @param string $locationType
1145 * @param $entityLabel
1146 *
1147 * @return string
1148 */
1149 protected function getOutputSpecificationIndex($key, $relationshipType, $locationType, $entityLabel) {
1150 if ($locationType || $entityLabel || $key === 'im') {
1151 // Just cos that's the history...
1152 if ($key !== 'master_id') {
1153 $key = $this->getHeaderForRow($key);
1154 }
1155 }
1156 if (!$relationshipType || $key !== 'id') {
1157 $key = $this->getMungedFieldName($key);
1158 }
1159 return $this->getMungedFieldName(
1160 ($relationshipType ? ($relationshipType . '_') : '')
1161 . ($locationType ? ($locationType . '_') : '')
1162 . $key
1163 . ($entityLabel ? ('_' . $entityLabel) : '')
1164 );
1165 }
1166
1167 /**
1168 * Get the compiled label for the column.
1169 *
1170 * e.g 'Gender', 'Employee Of-Home-city'
1171 *
1172 * @param string $key
1173 * @param string $relationshipType
1174 * @param string $locationType
1175 * @param string $entityLabel
1176 *
1177 * @return string
1178 */
1179 protected function getOutputSpecificationLabel($key, $relationshipType, $locationType, $entityLabel) {
1180 return ($relationshipType ? $this->getRelationshipTypes()[$relationshipType] . '-' : '')
1181 . ($locationType ? $locationType . '-' : '')
1182 . $this->getHeaderForRow($key)
1183 . ($entityLabel ? '-' . $entityLabel : '');
1184 }
1185
1186 /**
1187 * Get the mysql field name key.
1188 *
1189 * This key is locked in by tests but the reasons for the specific conventions -
1190 * ie. headings are used for keying fields in some cases, are likely
1191 * accidental rather than deliberate.
1192 *
1193 * This key is used for the output sql array.
1194 *
1195 * @param string $key
1196 * @param $relationshipType
1197 * @param $locationType
1198 * @param $entityLabel
1199 *
1200 * @return string
1201 */
1202 protected function getOutputSpecificationFieldKey($key, $relationshipType, $locationType, $entityLabel) {
1203 if ($relationshipType || $entityLabel || $key === 'im') {
1204 if ($key !== 'state_province' && $key !== 'id') {
1205 $key = $this->getHeaderForRow($key);
1206 }
1207 }
1208 if (!$relationshipType || $key !== 'id') {
1209 $key = $this->getMungedFieldName($key);
1210 }
1211 $fieldKey = $this->getMungedFieldName(
1212 ($relationshipType ? ($relationshipType . '_') : '')
1213 . ($locationType ? ($locationType . '_') : '')
1214 . $key
1215 . ($entityLabel ? ('_' . $entityLabel) : '')
1216 );
1217 return $fieldKey;
1218 }
1219
6003a964 1220}