3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2019
35 * Class CRM_Export_BAO_ExportProcessor
37 * Class to handle logic of export.
39 class CRM_Export_BAO_ExportProcessor
{
49 protected $exportMode;
52 * Array of fields in the main query.
56 protected $queryFields = [];
63 protected $queryOperator;
66 * Requested output fields.
68 * If set to NULL then it is 'primary fields only'
69 * which actually means pretty close to all fields!
73 protected $requestedFields;
76 * Is the contact being merged into a single household.
80 protected $isMergeSameHousehold;
83 * Only export contacts that can receive postal mail.
85 * Includes being alive, having an address & not having do_not_mail.
89 protected $isPostalableOnly;
92 * Key representing the head of household in the relationship array.
94 * e.g. ['8_b_a' => 'Household Member Is', '8_a_b = 'Household Member Of'.....]
98 protected $relationshipTypes = [];
101 * Array of properties to retrieve for relationships.
105 protected $relationshipReturnProperties = [];
108 * IDs of households that have already been exported.
112 protected $exportedHouseholds = [];
115 * Get return properties by relationship.
118 public function getRelationshipReturnProperties() {
119 return $this->relationshipReturnProperties
;
123 * Export values for related contacts.
127 protected $relatedContactValues = [];
132 protected $returnProperties = [];
137 protected $outputSpecification = [];
140 * CRM_Export_BAO_ExportProcessor constructor.
142 * @param int $exportMode
143 * @param array|NULL $requestedFields
144 * @param string $queryOperator
145 * @param bool $isMergeSameHousehold
146 * @param bool $isPostalableOnly
148 public function __construct($exportMode, $requestedFields, $queryOperator, $isMergeSameHousehold = FALSE, $isPostalableOnly = FALSE) {
149 $this->setExportMode($exportMode);
150 $this->setQueryMode();
151 $this->setQueryOperator($queryOperator);
152 $this->setRequestedFields($requestedFields);
153 $this->setRelationshipTypes();
154 $this->setIsMergeSameHousehold($isMergeSameHousehold);
155 $this->setisPostalableOnly($isPostalableOnly);
161 public function isPostalableOnly() {
162 return $this->isPostalableOnly
;
166 * @param bool $isPostalableOnly
168 public function setIsPostalableOnly($isPostalableOnly) {
169 $this->isPostalableOnly
= $isPostalableOnly;
174 public function getRequestedFields() {
175 return $this->requestedFields
;
179 * @param array|null $requestedFields
181 public function setRequestedFields($requestedFields) {
182 $this->requestedFields
= $requestedFields;
188 public function getReturnProperties() {
189 return $this->returnProperties
;
193 * @param array $returnProperties
195 public function setReturnProperties($returnProperties) {
196 $this->returnProperties
= $returnProperties;
202 public function getRelationshipTypes() {
203 return $this->relationshipTypes
;
208 public function setRelationshipTypes() {
209 $this->relationshipTypes
= CRM_Contact_BAO_Relationship
::getContactRelationshipType(
221 * Set the value for a relationship type field.
223 * In this case we are building up an array of properties for a related contact.
225 * These may be used for direct exporting or for merge to household depending on the
228 * @param string $relationshipType
229 * @param int $contactID
230 * @param string $field
231 * @param string $value
233 public function setRelationshipValue($relationshipType, $contactID, $field, $value) {
234 $this->relatedContactValues
[$relationshipType][$contactID][$field] = $value;
238 * Get the value for a relationship type field.
240 * In this case we are building up an array of properties for a related contact.
242 * These may be used for direct exporting or for merge to household depending on the
245 * @param string $relationshipType
246 * @param int $contactID
247 * @param string $field
251 public function getRelationshipValue($relationshipType, $contactID, $field) {
252 return isset($this->relatedContactValues
[$relationshipType][$contactID][$field]) ?
$this->relatedContactValues
[$relationshipType][$contactID][$field] : '';
256 * Get the id of the related household.
258 * @param int $contactID
259 * @param string $relationshipType
263 public function getRelatedHouseholdID($contactID, $relationshipType) {
264 return $this->relatedContactValues
[$relationshipType][$contactID]['id'];
268 * Has the household already been exported.
270 * @param int $housholdContactID
274 public function isHouseholdExported($housholdContactID) {
275 return isset($this->exportedHouseholds
[$housholdContactID]);
282 public function isMergeSameHousehold() {
283 return $this->isMergeSameHousehold
;
287 * @param bool $isMergeSameHousehold
289 public function setIsMergeSameHousehold($isMergeSameHousehold) {
290 $this->isMergeSameHousehold
= $isMergeSameHousehold;
294 * Return relationship types for household merge.
298 public function getHouseholdRelationshipTypes() {
299 if (!$this->isMergeSameHousehold()) {
303 CRM_Utils_Array
::key('Household Member of', $this->getRelationshipTypes()),
304 CRM_Utils_Array
::key('Head of Household for', $this->getRelationshipTypes()),
312 public function isRelationshipTypeKey($fieldName) {
313 return array_key_exists($fieldName, $this->relationshipTypes
);
321 public function isHouseholdMergeRelationshipTypeKey($fieldName) {
322 return in_array($fieldName, $this->getHouseholdRelationshipTypes());
328 public function getQueryOperator() {
329 return $this->queryOperator
;
333 * @param string $queryOperator
335 public function setQueryOperator($queryOperator) {
336 $this->queryOperator
= $queryOperator;
342 public function getQueryFields() {
343 return $this->queryFields
;
347 * @param array $queryFields
349 public function setQueryFields($queryFields) {
350 // legacy hacks - we add these to queryFields because this
351 // pseudometadata is currently required.
352 $queryFields['im_provider']['pseudoconstant']['var'] = 'imProviders';
353 $queryFields['country']['context'] = 'country';
354 $queryFields['world_region']['context'] = 'country';
355 $queryFields['state_province']['context'] = 'province';
356 $this->queryFields
= $queryFields;
362 public function getQueryMode() {
363 return $this->queryMode
;
367 * Set the query mode based on the export mode.
369 public function setQueryMode() {
371 switch ($this->getExportMode()) {
372 case CRM_Export_Form_Select
::CONTRIBUTE_EXPORT
:
373 $this->queryMode
= CRM_Contact_BAO_Query
::MODE_CONTRIBUTE
;
376 case CRM_Export_Form_Select
::EVENT_EXPORT
:
377 $this->queryMode
= CRM_Contact_BAO_Query
::MODE_EVENT
;
380 case CRM_Export_Form_Select
::MEMBER_EXPORT
:
381 $this->queryMode
= CRM_Contact_BAO_Query
::MODE_MEMBER
;
384 case CRM_Export_Form_Select
::PLEDGE_EXPORT
:
385 $this->queryMode
= CRM_Contact_BAO_Query
::MODE_PLEDGE
;
388 case CRM_Export_Form_Select
::CASE_EXPORT
:
389 $this->queryMode
= CRM_Contact_BAO_Query
::MODE_CASE
;
392 case CRM_Export_Form_Select
::GRANT_EXPORT
:
393 $this->queryMode
= CRM_Contact_BAO_Query
::MODE_GRANT
;
396 case CRM_Export_Form_Select
::ACTIVITY_EXPORT
:
397 $this->queryMode
= CRM_Contact_BAO_Query
::MODE_ACTIVITY
;
401 $this->queryMode
= CRM_Contact_BAO_Query
::MODE_CONTACTS
;
408 public function getExportMode() {
409 return $this->exportMode
;
413 * @param int $exportMode
415 public function setExportMode($exportMode) {
416 $this->exportMode
= $exportMode;
420 * Get the name for the export file.
424 public function getExportFileName() {
425 switch ($this->getExportMode()) {
426 case CRM_Export_Form_Select
::CONTACT_EXPORT
:
427 return ts('CiviCRM Contact Search');
429 case CRM_Export_Form_Select
::CONTRIBUTE_EXPORT
:
430 return ts('CiviCRM Contribution Search');
432 case CRM_Export_Form_Select
::MEMBER_EXPORT
:
433 return ts('CiviCRM Member Search');
435 case CRM_Export_Form_Select
::EVENT_EXPORT
:
436 return ts('CiviCRM Participant Search');
438 case CRM_Export_Form_Select
::PLEDGE_EXPORT
:
439 return ts('CiviCRM Pledge Search');
441 case CRM_Export_Form_Select
::CASE_EXPORT
:
442 return ts('CiviCRM Case Search');
444 case CRM_Export_Form_Select
::GRANT_EXPORT
:
445 return ts('CiviCRM Grant Search');
447 case CRM_Export_Form_Select
::ACTIVITY_EXPORT
:
448 return ts('CiviCRM Activity Search');
451 // Legacy code suggests the value could be 'financial' - ie. something
452 // other than what should be accepted. However, I suspect that this line is
454 return ts('CiviCRM Search');
459 * Get the label for the header row based on the field to output.
461 * @param string $field
465 public function getHeaderForRow($field) {
466 if (substr($field, -11) == 'campaign_id') {
467 // @todo - set this correctly in the xml rather than here.
468 // This will require a generalised handling cleanup
469 return ts('Campaign ID');
471 if ($this->isMergeSameHousehold() && $field === 'id') {
472 return ts('Household ID');
474 elseif (isset($this->getQueryFields()[$field]['title'])) {
475 return $this->getQueryFields()[$field]['title'];
477 elseif ($this->isExportPaymentFields() && array_key_exists($field, $this->getcomponentPaymentFields())) {
478 return CRM_Utils_Array
::value($field, $this->getcomponentPaymentFields());
488 * @param $returnProperties
491 public function runQuery($params, $order, $returnProperties) {
493 $params = array_merge($params, $this->getWhereParams());
494 if ($this->isPostalableOnly
) {
495 if (array_key_exists('street_address', $returnProperties)) {
496 $addressWhere = " civicrm_address.street_address <> ''";
497 if (array_key_exists('supplemental_address_1', $returnProperties)) {
498 // We need this to be an OR rather than AND on the street_address so, hack it in.
499 $addressOptions = CRM_Core_BAO_Setting
::valueOptions(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
500 'address_options', TRUE, NULL, TRUE
502 if (!empty($addressOptions['supplemental_address_1'])) {
503 $addressWhere .= " OR civicrm_address.supplemental_address_1 <> ''";
506 $addressWhere = ' AND (' . $addressWhere . ')';
509 $query = new CRM_Contact_BAO_Query($params, $returnProperties, NULL,
510 FALSE, FALSE, $this->getQueryMode(),
511 FALSE, TRUE, TRUE, NULL, $this->getQueryOperator()
516 $query->_sort
= $order;
517 list($select, $from, $where, $having) = $query->query();
518 $this->setQueryFields($query->_fields
);
519 return array($query, $select, $from, $where . $addressWhere, $having);
523 * Add a row to the specification for how to output data.
526 * @param string $relationshipType
527 * @param string $locationType
528 * @param int $entityTypeID phone_type_id or provider_id for phone or im fields.
530 public function addOutputSpecification($key, $relationshipType = NULL, $locationType = NULL, $entityTypeID = NULL) {
533 if ($key === 'phone') {
534 $entityLabel = CRM_Core_PseudoConstant
::getLabel('CRM_Core_BAO_Phone', 'phone_type_id', $entityTypeID);
537 $entityLabel = CRM_Core_PseudoConstant
::getLabel('CRM_Core_BAO_IM', 'provider_id', $entityTypeID);
541 // These oddly constructed keys are for legacy reasons. Altering them will affect test success
542 // but in time it may be good to rationalise them.
543 $label = $this->getOutputSpecificationLabel($key, $relationshipType, $locationType, $entityLabel);
544 $index = $this->getOutputSpecificationIndex($key, $relationshipType, $locationType, $entityLabel);
545 $fieldKey = $this->getOutputSpecificationFieldKey($key, $relationshipType, $locationType, $entityLabel);
547 $this->outputSpecification
[$index]['header'] = $label;
548 $this->outputSpecification
[$index]['sql_columns'] = $this->getSqlColumnDefinition($fieldKey, $key);
550 if ($relationshipType && $this->isHouseholdMergeRelationshipTypeKey($relationshipType)) {
551 $this->setColumnAsCalculationOnly($index);
553 $this->outputSpecification
[$index]['metadata'] = $this->getMetaDataForField($key);
557 * Get the metadata for the given field.
563 public function getMetaDataForField($key) {
564 $mappings = ['contact_id' => 'id'];
565 if (isset($this->getQueryFields()[$key])) {
566 return $this->getQueryFields()[$key];
568 if (isset($mappings[$key])) {
569 return $this->getQueryFields()[$mappings[$key]];
577 public function setSqlColumnDefn($key) {
578 $this->outputSpecification
[$this->getMungedFieldName($key)]['sql_columns'] = $this->getSqlColumnDefinition($key, $this->getMungedFieldName($key));
582 * Mark a column as only required for calculations.
584 * Do not include the row with headers.
586 * @param string $column
588 public function setColumnAsCalculationOnly($column) {
589 $this->outputSpecification
[$column]['do_not_output_to_csv'] = TRUE;
595 public function getHeaderRows() {
597 foreach ($this->outputSpecification
as $key => $spec) {
598 if (empty($spec['do_not_output_to_csv'])) {
599 $headerRows[] = $spec['header'];
608 public function getSQLColumns() {
610 foreach ($this->outputSpecification
as $key => $spec) {
611 if (empty($spec['do_not_output_to_sql'])) {
612 $sqlColumns[$key] = $spec['sql_columns'];
621 public function getMetadata() {
623 foreach ($this->outputSpecification
as $key => $spec) {
624 $metadata[$key] = $spec['metadata'];
630 * Build the row for output.
632 * @param \CRM_Contact_BAO_Query $query
633 * @param CRM_Core_DAO $iterationDAO
634 * @param array $outputColumns
636 * @param $paymentDetails
637 * @param $addPaymentHeader
638 * @param $paymentTableId
642 public function buildRow($query, $iterationDAO, $outputColumns, $metadata, $paymentDetails, $addPaymentHeader, $paymentTableId) {
643 $phoneTypes = CRM_Core_PseudoConstant
::get('CRM_Core_DAO_Phone', 'phone_type_id');
644 $imProviders = CRM_Core_PseudoConstant
::get('CRM_Core_DAO_IM', 'provider_id');
647 $householdMergeRelationshipType = $this->getHouseholdMergeTypeForRow($iterationDAO->contact_id
);
648 if ($householdMergeRelationshipType) {
649 $householdID = $this->getRelatedHouseholdID($iterationDAO->contact_id
, $householdMergeRelationshipType);
650 if ($this->isHouseholdExported($householdID)) {
653 foreach (array_keys($outputColumns) as $column) {
654 $row[$column] = $this->getRelationshipValue($householdMergeRelationshipType, $iterationDAO->contact_id
, $column);
656 $this->markHouseholdExported($householdID);
660 $query->convertToPseudoNames($iterationDAO);
662 //first loop through output columns so that we return what is required, and in same order.
663 foreach ($outputColumns as $field => $value) {
664 // add im_provider to $dao object
665 if ($field == 'im_provider' && property_exists($iterationDAO, 'provider_id')) {
666 $iterationDAO->im_provider
= $iterationDAO->provider_id
;
669 //build row values (data)
671 if (property_exists($iterationDAO, $field)) {
672 $fieldValue = $iterationDAO->$field;
673 // to get phone type from phone type id
674 if ($field == 'phone_type_id' && isset($phoneTypes[$fieldValue])) {
675 $fieldValue = $phoneTypes[$fieldValue];
677 elseif ($field == 'provider_id' ||
$field == 'im_provider') {
678 $fieldValue = CRM_Utils_Array
::value($fieldValue, $imProviders);
680 elseif (strstr($field, 'master_id')) {
681 $masterAddressId = NULL;
682 if (isset($iterationDAO->$field)) {
683 $masterAddressId = $iterationDAO->$field;
685 // get display name of contact that address is shared.
686 $fieldValue = CRM_Contact_BAO_Contact
::getMasterDisplayName($masterAddressId);
690 if ($this->isRelationshipTypeKey($field)) {
691 $this->buildRelationshipFieldsForRow($row, $iterationDAO->contact_id
, $value, $field);
694 $row[$field] = $this->getTransformedFieldValue($field, $iterationDAO, $fieldValue, $metadata, $paymentDetails);
698 // If specific payment fields have been selected for export, payment
699 // data will already be in $row. Otherwise, add payment related
700 // information, if appropriate.
701 if ($addPaymentHeader) {
702 if (!$this->isExportSpecifiedPaymentFields()) {
703 $nullContributionDetails = array_fill_keys(array_keys($this->getPaymentHeaders()), NULL);
704 if ($this->isExportPaymentFields()) {
705 $paymentData = CRM_Utils_Array
::value($row[$paymentTableId], $paymentDetails);
706 if (!is_array($paymentData) ||
empty($paymentData)) {
707 $paymentData = $nullContributionDetails;
709 $row = array_merge($row, $paymentData);
711 elseif (!empty($paymentDetails)) {
712 $row = array_merge($row, $nullContributionDetails);
716 //remove organization name for individuals if it is set for current employer
717 if (!empty($row['contact_type']) &&
718 $row['contact_type'] == 'Individual' && array_key_exists('organization_name', $row)
720 $row['organization_name'] = '';
726 * If this row has a household whose details we should use get the relationship type key.
732 public function getHouseholdMergeTypeForRow($contactID) {
733 if (!$this->isMergeSameHousehold()) {
736 foreach ($this->getHouseholdRelationshipTypes() as $relationshipType) {
737 if (isset($this->relatedContactValues
[$relationshipType][$contactID])) {
738 return $relationshipType;
744 * Mark the given household as already exported.
746 * @param $householdID
748 public function markHouseholdExported($householdID) {
749 $this->exportedHouseholds
[$householdID] = $householdID;
754 * @param $iterationDAO
757 * @param $paymentDetails
761 public function getTransformedFieldValue($field, $iterationDAO, $fieldValue, $metadata, $paymentDetails) {
763 $i18n = CRM_Core_I18n
::singleton();
764 if ($field == 'id') {
765 return $iterationDAO->contact_id
;
766 // special case for calculated field
768 elseif ($field == 'source_contact_id') {
769 return $iterationDAO->contact_id
;
771 elseif ($field == 'pledge_balance_amount') {
772 return $iterationDAO->pledge_amount
- $iterationDAO->pledge_total_paid
;
773 // special case for calculated field
775 elseif ($field == 'pledge_next_pay_amount') {
776 return $iterationDAO->pledge_next_pay_amount +
$iterationDAO->pledge_outstanding_amount
;
778 elseif (isset($fieldValue) &&
781 //check for custom data
782 if ($cfID = CRM_Core_BAO_CustomField
::getKeyID($field)) {
783 return CRM_Core_BAO_CustomField
::displayValue($fieldValue, $cfID);
786 elseif (in_array($field, array(
791 //special case for greeting replacement
792 $fldValue = "{$field}_display";
793 return $iterationDAO->$fldValue;
796 //normal fields with a touch of CRM-3157
800 return $i18n->crm_translate($fieldValue, array('context' => 'country'));
802 case 'state_province':
803 return $i18n->crm_translate($fieldValue, array('context' => 'province'));
806 case 'preferred_communication_method':
807 case 'preferred_mail_format':
808 case 'communication_style':
809 return $i18n->crm_translate($fieldValue);
812 if (isset($metadata[$field])) {
813 // No I don't know why we do it this way & whether we could
814 // make better use of pseudoConstants.
815 if (!empty($metadata[$field]['context'])) {
816 return $i18n->crm_translate($fieldValue, $metadata[$field]);
818 if (!empty($metadata[$field]['pseudoconstant'])) {
819 if (!empty($metadata[$field]['bao'])) {
820 return CRM_Core_PseudoConstant
::getLabel($metadata[$field]['bao'], $metadata[$field]['name'], $fieldValue);
822 // This is not our normal syntax for pseudoconstants but I am a bit loath to
823 // call an external function until sure it is not increasing php processing given this
824 // may be iterated 100,000 times & we already have the $imProvider var loaded.
825 // That can be next refactor...
826 // Yes - definitely feeling hatred for this bit of code - I know you will beat me up over it's awfulness
827 // but I have to reach a stable point....
828 $varName = $metadata[$field]['pseudoconstant']['var'];
829 if ($varName === 'imProviders') {
830 return CRM_Core_PseudoConstant
::getLabel('CRM_Core_DAO_IM', 'provider_id', $fieldValue);
832 if ($varName === 'phoneTypes') {
833 return CRM_Core_PseudoConstant
::getLabel('CRM_Core_DAO_Phone', 'phone_type_id', $fieldValue);
842 elseif ($this->isExportSpecifiedPaymentFields() && array_key_exists($field, $this->getcomponentPaymentFields())) {
843 $paymentTableId = $this->getPaymentTableID();
844 $paymentData = CRM_Utils_Array
::value($iterationDAO->$paymentTableId, $paymentDetails);
845 $payFieldMapper = array(
846 'componentPaymentField_total_amount' => 'total_amount',
847 'componentPaymentField_contribution_status' => 'contribution_status',
848 'componentPaymentField_payment_instrument' => 'pay_instru',
849 'componentPaymentField_transaction_id' => 'trxn_id',
850 'componentPaymentField_received_date' => 'receive_date',
852 return CRM_Utils_Array
::value($payFieldMapper[$field], $paymentData, '');
855 // if field is empty or null
861 * Get array of fields to return, over & above those defined in the main contact exportable fields.
863 * These include export mode specific fields & some fields apparently required as 'exportableFields'
864 * but not returned by the function of the same name.
867 * Array of fields to return in the format ['field_name' => 1,...]
869 public function getAdditionalReturnProperties() {
870 if ($this->getQueryMode() === CRM_Contact_BAO_Query
::MODE_CONTACTS
) {
871 $componentSpecificFields = [];
874 $componentSpecificFields = CRM_Contact_BAO_Query
::defaultReturnProperties($this->getQueryMode());
876 if ($this->getQueryMode() === CRM_Contact_BAO_Query
::MODE_PLEDGE
) {
877 $componentSpecificFields = array_merge($componentSpecificFields, CRM_Pledge_BAO_Query
::extraReturnProperties($this->getQueryMode()));
878 unset($componentSpecificFields['contribution_status_id']);
879 unset($componentSpecificFields['pledge_status_id']);
880 unset($componentSpecificFields['pledge_payment_status_id']);
882 if ($this->getQueryMode() === CRM_Contact_BAO_Query
::MODE_CASE
) {
883 $componentSpecificFields = array_merge($componentSpecificFields, CRM_Case_BAO_Query
::extraReturnProperties($this->getQueryMode()));
885 if ($this->getQueryMode() === CRM_Contact_BAO_Query
::MODE_CONTRIBUTE
) {
886 $componentSpecificFields = array_merge($componentSpecificFields, CRM_Contribute_BAO_Query
::softCreditReturnProperties(TRUE));
887 unset($componentSpecificFields['contribution_status_id']);
889 return $componentSpecificFields;
893 * Should payment fields be appended to the export.
895 * (This is pretty hacky so hopefully this function won't last long - notice
896 * how obviously it should be part of the above function!).
898 public function isExportPaymentFields() {
899 if ($this->getRequestedFields() === NULL
900 && in_array($this->getQueryMode(), [
901 CRM_Contact_BAO_Query
::MODE_EVENT
,
902 CRM_Contact_BAO_Query
::MODE_MEMBER
,
903 CRM_Contact_BAO_Query
::MODE_PLEDGE
,
907 elseif ($this->isExportSpecifiedPaymentFields()) {
914 * Has specific payment fields been requested (as opposed to via all fields).
916 * If specific fields have been requested then they get added at various points.
920 public function isExportSpecifiedPaymentFields() {
921 if ($this->getRequestedFields() !== NULL && $this->hasRequestedComponentPaymentFields()) {
927 * Get the name of the id field in the table that connects contributions to the export entity.
929 public function getPaymentTableID() {
930 if ($this->getRequestedFields() === NULL) {
932 CRM_Contact_BAO_Query
::MODE_EVENT
=> 'participant_id',
933 CRM_Contact_BAO_Query
::MODE_MEMBER
=> 'membership_id',
934 CRM_Contact_BAO_Query
::MODE_PLEDGE
=> 'pledge_payment_id',
936 return isset($mapping[$this->getQueryMode()]) ?
$mapping[$this->getQueryMode()] : '';
938 elseif ($this->hasRequestedComponentPaymentFields()) {
939 return 'participant_id';
945 * Have component payment fields been requested.
949 protected function hasRequestedComponentPaymentFields() {
950 if ($this->getQueryMode() === CRM_Contact_BAO_Query
::MODE_EVENT
) {
951 $participantPaymentFields = array_intersect_key($this->getComponentPaymentFields(), $this->getReturnProperties());
952 if (!empty($participantPaymentFields)) {
960 * Get fields that indicate payment fields have been requested for a component.
962 * Ideally this should be protected but making it temporarily public helps refactoring..
966 public function getComponentPaymentFields() {
968 'componentPaymentField_total_amount' => ts('Total Amount'),
969 'componentPaymentField_contribution_status' => ts('Contribution Status'),
970 'componentPaymentField_received_date' => ts('Date Received'),
971 'componentPaymentField_payment_instrument' => ts('Payment Method'),
972 'componentPaymentField_transaction_id' => ts('Transaction ID'),
977 * Get headers for payment fields.
979 * Returns an array of contribution fields when the entity supports payment fields and specific fields
980 * are not specified. This is a transitional function for refactoring legacy code.
982 public function getPaymentHeaders() {
983 if ($this->isExportPaymentFields() && !$this->isExportSpecifiedPaymentFields()) {
984 return $this->getcomponentPaymentFields();
990 * Get the default properties when not specified.
992 * In the UI this appears as 'Primary fields only' but in practice it's
993 * most of the kitchen sink and the hallway closet thrown in.
995 * Since CRM-952 custom fields are excluded, but no other form of mercy is shown.
999 public function getDefaultReturnProperties() {
1000 $returnProperties = [];
1001 $fields = CRM_Contact_BAO_Contact
::exportableFields('All', TRUE, TRUE);
1002 $skippedFields = ($this->getQueryMode() === CRM_Contact_BAO_Query
::MODE_CONTACTS
) ?
[] : [
1008 foreach ($fields as $key => $var) {
1009 if ($key && (substr($key, 0, 6) != 'custom') && !in_array($key, $skippedFields)) {
1010 $returnProperties[$key] = 1;
1013 $returnProperties = array_merge($returnProperties, $this->getAdditionalReturnProperties());
1014 return $returnProperties;
1018 * Add the field to relationship return properties & return it.
1020 * This function is doing both setting & getting which is yuck but it is an interim
1023 * @param array $value
1024 * @param string $relationshipKey
1028 public function setRelationshipReturnProperties($value, $relationshipKey) {
1029 $relPhoneTypeId = $relIMProviderId = NULL;
1030 if (!empty($value[2])) {
1031 $relationField = CRM_Utils_Array
::value(2, $value);
1032 if (trim(CRM_Utils_Array
::value(3, $value))) {
1033 $relLocTypeId = CRM_Utils_Array
::value(3, $value);
1036 $relLocTypeId = 'Primary';
1039 if ($relationField == 'phone') {
1040 $relPhoneTypeId = CRM_Utils_Array
::value(4, $value);
1042 elseif ($relationField == 'im') {
1043 $relIMProviderId = CRM_Utils_Array
::value(4, $value);
1046 elseif (!empty($value[4])) {
1047 $relationField = CRM_Utils_Array
::value(4, $value);
1048 $relLocTypeId = CRM_Utils_Array
::value(5, $value);
1049 if ($relationField == 'phone') {
1050 $relPhoneTypeId = CRM_Utils_Array
::value(6, $value);
1052 elseif ($relationField == 'im') {
1053 $relIMProviderId = CRM_Utils_Array
::value(6, $value);
1056 if (in_array($relationField, $this->getValidLocationFields()) && is_numeric($relLocTypeId)) {
1057 $locationName = CRM_Core_PseudoConstant
::getName('CRM_Core_BAO_Address', 'location_type_id', $relLocTypeId);
1058 if ($relPhoneTypeId) {
1059 $this->relationshipReturnProperties
[$relationshipKey]['location'][$locationName]['phone-' . $relPhoneTypeId] = 1;
1061 elseif ($relIMProviderId) {
1062 $this->relationshipReturnProperties
[$relationshipKey]['location'][$locationName]['im-' . $relIMProviderId] = 1;
1065 $this->relationshipReturnProperties
[$relationshipKey]['location'][$locationName][$relationField] = 1;
1069 $this->relationshipReturnProperties
[$relationshipKey][$relationField] = 1;
1071 return $this->relationshipReturnProperties
[$relationshipKey];
1075 * Add the main return properties to the household merge properties if needed for merging.
1077 * If we are using household merge we need to add these to the relationship properties to
1080 * @param $returnProperties
1082 public function setHouseholdMergeReturnProperties($returnProperties) {
1083 foreach ($this->getHouseholdRelationshipTypes() as $householdRelationshipType) {
1084 $this->relationshipReturnProperties
[$householdRelationshipType] = $returnProperties;
1089 * Get the default location fields to request.
1093 public function getValidLocationFields() {
1096 'supplemental_address_1',
1097 'supplemental_address_2',
1098 'supplemental_address_3',
1101 'postal_code_suffix',
1113 * Get the sql column definition for the given field.
1115 * @param string $fieldName
1116 * @param string $columnName
1120 public function getSqlColumnDefinition($fieldName, $columnName) {
1122 // early exit for master_id, CRM-12100
1123 // in the DB it is an ID, but in the export, we retrive the display_name of the master record
1124 // also for current_employer, CRM-16939
1125 if ($columnName == 'master_id' ||
$columnName == 'current_employer') {
1126 return "$fieldName varchar(128)";
1129 if (substr($fieldName, -11) == 'campaign_id') {
1131 return "$fieldName varchar(128)";
1134 $queryFields = $this->getQueryFields();
1135 $lookUp = ['prefix_id', 'suffix_id'];
1136 // set the sql columns
1137 if (isset($queryFields[$columnName]['type'])) {
1138 switch ($queryFields[$columnName]['type']) {
1139 case CRM_Utils_Type
::T_INT
:
1140 case CRM_Utils_Type
::T_BOOLEAN
:
1141 if (in_array($columnName, $lookUp)) {
1142 return "$fieldName varchar(255)";
1145 return "$fieldName varchar(16)";
1148 case CRM_Utils_Type
::T_STRING
:
1149 if (isset($queryFields[$columnName]['maxlength'])) {
1150 return "$fieldName varchar({$queryFields[$columnName]['maxlength']})";
1153 return "$fieldName varchar(255)";
1156 case CRM_Utils_Type
::T_TEXT
:
1157 case CRM_Utils_Type
::T_LONGTEXT
:
1158 case CRM_Utils_Type
::T_BLOB
:
1159 case CRM_Utils_Type
::T_MEDIUMBLOB
:
1160 return "$fieldName longtext";
1162 case CRM_Utils_Type
::T_FLOAT
:
1163 case CRM_Utils_Type
::T_ENUM
:
1164 case CRM_Utils_Type
::T_DATE
:
1165 case CRM_Utils_Type
::T_TIME
:
1166 case CRM_Utils_Type
::T_TIMESTAMP
:
1167 case CRM_Utils_Type
::T_MONEY
:
1168 case CRM_Utils_Type
::T_EMAIL
:
1169 case CRM_Utils_Type
::T_URL
:
1170 case CRM_Utils_Type
::T_CCNUM
:
1172 return "$fieldName varchar(32)";
1176 if (substr($fieldName, -3, 3) == '_id') {
1177 return "$fieldName varchar(255)";
1179 elseif (substr($fieldName, -5, 5) == '_note') {
1180 return "$fieldName text";
1189 if (in_array($fieldName, $changeFields)) {
1190 return "$fieldName text";
1193 // set the sql columns for custom data
1194 if (isset($queryFields[$columnName]['data_type'])) {
1196 switch ($queryFields[$columnName]['data_type']) {
1198 // May be option labels, which could be up to 512 characters
1199 $length = max(512, CRM_Utils_Array
::value('text_length', $queryFields[$columnName]));
1200 return "$fieldName varchar($length)";
1203 case 'StateProvince':
1205 return "$fieldName varchar(255)";
1208 return "$fieldName text";
1211 return "$fieldName varchar(255)";
1215 return "$fieldName text";
1223 * Get the munged field name.
1225 * @param string $field
1228 public function getMungedFieldName($field) {
1229 $fieldName = CRM_Utils_String
::munge(strtolower($field), '_', 64);
1230 if ($fieldName == 'id') {
1231 $fieldName = 'civicrm_primary_id';
1237 * In order to respect the history of this class we need to index kinda illogically.
1239 * On the bright side - this stuff is tested within a nano-byte of it's life.
1241 * e.g '2-a-b_Home-City'
1243 * @param string $key
1244 * @param string $relationshipType
1245 * @param string $locationType
1246 * @param $entityLabel
1250 protected function getOutputSpecificationIndex($key, $relationshipType, $locationType, $entityLabel) {
1251 if ($entityLabel ||
$key === 'im') {
1252 // Just cos that's the history...
1253 if ($key !== 'master_id') {
1254 $key = $this->getHeaderForRow($key);
1257 if (!$relationshipType ||
$key !== 'id') {
1258 $key = $this->getMungedFieldName($key);
1260 return $this->getMungedFieldName(
1261 ($relationshipType ?
($relationshipType . '_') : '')
1262 . ($locationType ?
($locationType . '_') : '')
1264 . ($entityLabel ?
('_' . $entityLabel) : '')
1269 * Get the compiled label for the column.
1271 * e.g 'Gender', 'Employee Of-Home-city'
1273 * @param string $key
1274 * @param string $relationshipType
1275 * @param string $locationType
1276 * @param string $entityLabel
1280 protected function getOutputSpecificationLabel($key, $relationshipType, $locationType, $entityLabel) {
1281 return ($relationshipType ?
$this->getRelationshipTypes()[$relationshipType] . '-' : '')
1282 . ($locationType ?
$locationType . '-' : '')
1283 . $this->getHeaderForRow($key)
1284 . ($entityLabel ?
'-' . $entityLabel : '');
1288 * Get the mysql field name key.
1290 * This key is locked in by tests but the reasons for the specific conventions -
1291 * ie. headings are used for keying fields in some cases, are likely
1292 * accidental rather than deliberate.
1294 * This key is used for the output sql array.
1296 * @param string $key
1297 * @param $relationshipType
1298 * @param $locationType
1299 * @param $entityLabel
1303 protected function getOutputSpecificationFieldKey($key, $relationshipType, $locationType, $entityLabel) {
1304 if ($entityLabel ||
$key === 'im') {
1305 if ($key !== 'state_province' && $key !== 'id') {
1306 // @todo - test removing this - indexing by $key should be fine...
1307 $key = $this->getHeaderForRow($key);
1310 if (!$relationshipType ||
$key !== 'id') {
1311 $key = $this->getMungedFieldName($key);
1313 $fieldKey = $this->getMungedFieldName(
1314 ($relationshipType ?
($relationshipType . '_') : '')
1315 . ($locationType ?
($locationType . '_') : '')
1317 . ($entityLabel ?
('_' . $entityLabel) : '')
1323 * Get params for the where criteria.
1327 public function getWhereParams() {
1328 if (!$this->isPostalableOnly()) {
1331 $params['is_deceased'] = ['is_deceased', '=', 0, CRM_Contact_BAO_Query
::MODE_CONTACTS
];
1332 $params['do_not_mail'] = ['do_not_mail', '=', 0, CRM_Contact_BAO_Query
::MODE_CONTACTS
];
1342 protected function buildRelationshipFieldsForRow(&$row, $contactID, $value, $field) {
1343 foreach (array_keys($value) as $property) {
1344 if ($property === 'location') {
1345 // @todo just undo all this nasty location wrangling!
1346 foreach ($value['location'] as $locationKey => $locationFields) {
1347 foreach (array_keys($locationFields) as $locationField) {
1348 $fieldKey = str_replace(' ', '_', $locationKey . '-' . $locationField);
1349 $row[$field . '_' . $fieldKey] = $this->getRelationshipValue($field, $contactID, $fieldKey);
1354 $row[$field . '_' . $property] = $this->getRelationshipValue($field, $contactID, $property);