Merge branch 'master' into dev_5

This commit is contained in:
Jens Schuppe 2020-01-22 11:59:43 +01:00
commit cd7c7e7d12
16 changed files with 772 additions and 186 deletions

View file

@ -44,6 +44,76 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
*/ */
protected static $_paymentInstruments = NULL; protected static $_paymentInstruments = NULL;
/**
* @var array
*
* A static cache of retrieved contribution statuses found within
* static::getContributionStatusOptions().
*/
protected static $_contributionStatusOptions = NULL;
/**
* @var array
*
* A static cache of retrieved groups found within static::getGroups().
*/
protected static $_groups = NULL;
/**
* @var array
*
* A static cache of retrieved newsletter groups found within
* static::getNewsletterGroups().
*/
protected static $_newsletterGroups = NULL;
/**
* @var array
*
* A static cache of retrieved campaigns found within static::getCampaigns().
*/
protected static $_campaigns = NULL;
/**
* @var array
*
* A static cache of retrieved financial types found within
* static::getFinancialTypes().
*/
protected static $_financialTypes = NULL;
/**
* @var array
*
* A static cache of retrieved genders found within
* static::getGenderOptions().
*/
protected static $_genderOptions = NULL;
/**
* @var array
*
* A static cache of retrieved location types found within
* static::getLocationTypes().
*/
protected static $_locationTypes = NULL;
/**
* @var array
*
* A static cache of retrieved membership types found within
* static::getMembershipTypes().
*/
protected static $_membershipTypes = NULL;
/**
* @var array
*
* A static cache of retrieved CiviSEPA creditors found within
* static::getSepaCreditors().
*/
protected static $_sepaCreditors = NULL;
/** /**
* Builds the form structure. * Builds the form structure.
*/ */
@ -59,10 +129,6 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
$profile_name = NULL; $profile_name = NULL;
} }
// Assign template variables.
$this->assign('op', $this->_op);
$this->assign('profile_name', $profile_name);
// Set redirect destination. // Set redirect destination.
$this->controller->_destination = CRM_Utils_System::url('civicrm/admin/settings/twingle/profiles', 'reset=1'); $this->controller->_destination = CRM_Utils_System::url('civicrm/admin/settings/twingle/profiles', 'reset=1');
@ -88,6 +154,21 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
} }
CRM_Utils_System::setTitle(E::ts('Edit Twingle API profile <em>%1</em>', array(1 => $this->profile->getName()))); CRM_Utils_System::setTitle(E::ts('Edit Twingle API profile <em>%1</em>', array(1 => $this->profile->getName())));
break; break;
case 'copy':
// This will be a 'create' actually.
$this->_op = 'create';
// When copying without a valid profile name, copy the default profile.
if (!$profile_name) {
$profile_name = 'default';
$this->profile = CRM_Twingle_Profile::getProfile($profile_name);
}
// Set a new name for this profile.
$profile_name = $profile_name . '_copy';
$this->profile->setName($profile_name);
CRM_Utils_System::setTitle(E::ts('New Twingle API profile'));
break;
case 'create': case 'create':
// Load factory default profile values. // Load factory default profile values.
$this->profile = CRM_twingle_Profile::createDefaultProfile($profile_name); $this->profile = CRM_twingle_Profile::createDefaultProfile($profile_name);
@ -95,6 +176,10 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
break; break;
} }
// Assign template variables.
$this->assign('op', $this->_op);
$this->assign('profile_name', $profile_name);
// Add form elements. // Add form elements.
$is_default = $profile_name == 'default'; $is_default = $profile_name == 'default';
$this->add( $this->add(
@ -109,7 +194,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'text', // field type 'text', // field type
'selector', // field name 'selector', // field name
E::ts('Project IDs'), // field label E::ts('Project IDs'), // field label
array(), ['class' => 'huge'],
TRUE // is required TRUE // is required
); );
@ -117,7 +202,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'select', 'select',
'location_type_id', 'location_type_id',
E::ts('Location type'), E::ts('Location type'),
$this->getLocationTypes(), static::getLocationTypes(),
TRUE TRUE
); );
@ -125,7 +210,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'select', 'select',
'location_type_id_organisation', 'location_type_id_organisation',
E::ts('Location type for organisations'), E::ts('Location type for organisations'),
$this->getLocationTypes(), static::getLocationTypes(),
TRUE TRUE
); );
@ -133,14 +218,14 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'select', // field type 'select', // field type
'financial_type_id', // field name 'financial_type_id', // field name
E::ts('Financial type'), // field label E::ts('Financial type'), // field label
$this->getFinancialTypes(), // list of options static::getFinancialTypes(), // list of options
TRUE // is required TRUE // is required
); );
$this->add( $this->add(
'select', // field type 'select', // field type
'financial_type_id_recur', // field name 'financial_type_id_recur', // field name
E::ts('Financial type (recurring)'), // field label E::ts('Financial type (recurring)'), // field label
$this->getFinancialTypes(), // list of options static::getFinancialTypes(), // list of options
TRUE // is required TRUE // is required
); );
@ -148,21 +233,21 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'select', 'select',
'gender_male', 'gender_male',
E::ts('Gender option for submitted value "male"'), E::ts('Gender option for submitted value "male"'),
$this->getGenderOptions(), static::getGenderOptions(),
TRUE TRUE
); );
$this->add( $this->add(
'select', 'select',
'gender_female', 'gender_female',
E::ts('Gender option for submitted value "female"'), E::ts('Gender option for submitted value "female"'),
$this->getGenderOptions(), static::getGenderOptions(),
TRUE TRUE
); );
$this->add( $this->add(
'select', 'select',
'gender_other', 'gender_other',
E::ts('Gender option for submitted value "other"'), E::ts('Gender option for submitted value "other"'),
$this->getGenderOptions(), static::getGenderOptions(),
TRUE TRUE
); );
@ -173,9 +258,17 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'select', // field type 'select', // field type
$pi_name, // field name $pi_name, // field name
E::ts('Record %1 as', array(1 => $pi_label)), // field label E::ts('Record %1 as', array(1 => $pi_label)), // field label
$this->getPaymentInstruments(), // list of options static::getPaymentInstruments(), // list of options
TRUE // is required TRUE // is required
); );
$this->add(
'select',
$pi_name . '_status',
E::ts('Record %1 donations with contribution status', array(1 => $pi_label)),
static::getContributionStatusOptions(),
TRUE
);
} }
if (CRM_Twingle_Submission::civiSepaEnabled()) { if (CRM_Twingle_Submission::civiSepaEnabled()) {
@ -183,7 +276,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'select', 'select',
'sepa_creditor_id', 'sepa_creditor_id',
E::ts('CiviSEPA creditor'), E::ts('CiviSEPA creditor'),
$this->getSepaCreditors(), static::getSepaCreditors(),
TRUE TRUE
); );
} }
@ -192,7 +285,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'select', // field type 'select', // field type
'newsletter_groups', // field name 'newsletter_groups', // field name
E::ts('Sign up for newsletter groups'), // field label E::ts('Sign up for newsletter groups'), // field label
$this->getNewsletterGroups(), // list of options static::getNewsletterGroups(), // list of options
FALSE, // is not required FALSE, // is not required
array('class' => 'crm-select2 huge', 'multiple' => 'multiple') array('class' => 'crm-select2 huge', 'multiple' => 'multiple')
); );
@ -201,7 +294,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'select', // field type 'select', // field type
'postinfo_groups', // field name 'postinfo_groups', // field name
E::ts('Sign up for postal mail groups'), // field label E::ts('Sign up for postal mail groups'), // field label
$this->getPostinfoGroups(), // list of options static::getPostinfoGroups(), // list of options
FALSE, // is not required FALSE, // is not required
array('class' => 'crm-select2 huge', 'multiple' => 'multiple') array('class' => 'crm-select2 huge', 'multiple' => 'multiple')
); );
@ -210,7 +303,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'select', // field type 'select', // field type
'donation_receipt_groups', // field name 'donation_receipt_groups', // field name
E::ts('Sign up for Donation receipt groups'), // field label E::ts('Sign up for Donation receipt groups'), // field label
$this->getDonationReceiptGroups(), // list of options static::getDonationReceiptGroups(), // list of options
FALSE, // is not required FALSE, // is not required
array('class' => 'crm-select2 huge', 'multiple' => 'multiple') array('class' => 'crm-select2 huge', 'multiple' => 'multiple')
); );
@ -219,7 +312,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'select', // field type 'select', // field type
'campaign', // field name 'campaign', // field name
E::ts('Assign donation to campaign'), // field label E::ts('Assign donation to campaign'), // field label
array('' => E::ts('- none -')) + $this->getCampaigns(), // list of options array('' => E::ts('- none -')) + static::getCampaigns(), // list of options
FALSE, // is not required FALSE, // is not required
array('class' => 'crm-select2 huge') array('class' => 'crm-select2 huge')
); );
@ -228,7 +321,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'select', // field type 'select', // field type
'membership_type_id', // field name 'membership_type_id', // field name
E::ts('Create membership of type'), // field label E::ts('Create membership of type'), // field label
array('' => E::ts('- none -')) + $this->getMembershipTypes(), // list of options array('' => E::ts('- none -')) + static::getMembershipTypes(), // list of options
FALSE, // is not required FALSE, // is not required
array('class' => 'crm-select2 huge') array('class' => 'crm-select2 huge')
); );
@ -240,6 +333,13 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
array() array()
); );
$this->add(
'textarea', // field type
'custom_field_mapping', // field name
E::ts('Custom field mapping'), // field label
array()
);
$this->addButtons(array( $this->addButtons(array(
array( array(
'type' => 'submit', 'type' => 'submit',
@ -277,6 +377,69 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
$errors['name'] = E::ts('Only alphanumeric characters and the underscore (_) are allowed for profile names.'); $errors['name'] = E::ts('Only alphanumeric characters and the underscore (_) are allowed for profile names.');
} }
// Validate custom field mapping.
try {
if (isset($values['custom_field_mapping'])) {
$custom_field_mapping = preg_split('/\r\n|\r|\n/', $values['custom_field_mapping'], -1, PREG_SPLIT_NO_EMPTY);
if (!is_array($custom_field_mapping)) {
throw new Exception(
E::ts('Could not parse custom field mapping.')
);
}
foreach ($custom_field_mapping as $custom_field_map) {
$custom_field_map = explode("=", $custom_field_map);
if (count($custom_field_map) !== 2) {
throw new Exception(
E::ts('Could not parse custom field mapping.')
);
}
list($twingle_field_name, $custom_field_name) = $custom_field_map;
$custom_field_id = substr($custom_field_name, strlen('custom_'));
// Check for custom field existence
try {
$custom_field = civicrm_api3('CustomField', 'getsingle', array(
'id' => $custom_field_id,
));
}
catch (CiviCRM_API3_Exception $exception) {
throw new Exception(
E::ts(
'Custom field custom_%1 does not exist.',
array(1 => $custom_field_id)
)
);
}
// Only allow custom fields on relevant entities.
try {
$custom_group = civicrm_api3('CustomGroup', 'getsingle', array(
'id' => $custom_field['custom_group_id'],
'extends' => array(
'IN' => array(
'Contact',
'Individual',
'Organization',
'Contribution',
'ContributionRecur',
),
),
));
} catch (CiviCRM_API3_Exception $exception) {
throw new Exception(
E::ts(
'Custom field custom_%1 is not in a CustomGroup that extends one of the supported CiviCRM entities.',
array(1 => $custom_field['id'])
)
);
}
}
}
}
catch (Exception $exception) {
$errors['custom_field_mapping'] = $exception->getMessage();
}
return empty($errors) ? TRUE : $errors; return empty($errors) ? TRUE : $errors;
} }
@ -325,16 +488,18 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
* *
* @throws \CiviCRM_API3_Exception * @throws \CiviCRM_API3_Exception
*/ */
public function getLocationTypes() { public static function getLocationTypes() {
$location_types = array(); if (!isset(static::$_locationTypes)) {
static::$_locationTypes = array();
$query = civicrm_api3('LocationType', 'get', array( $query = civicrm_api3('LocationType', 'get', array(
'option.limit' => 0,
'is_active' => 1, 'is_active' => 1,
)); ));
foreach ($query['values'] as $type) { foreach ($query['values'] as $type) {
$location_types[$type['id']] = $type['name']; static::$_locationTypes[$type['id']] = $type['name'];
} }
}
return $location_types; return static::$_locationTypes;
} }
/** /**
@ -345,17 +510,19 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
* *
* @throws \CiviCRM_API3_Exception * @throws \CiviCRM_API3_Exception
*/ */
public function getFinancialTypes() { public static function getFinancialTypes() {
$financial_types = array(); if (!isset(static::$_financialTypes)) {
static::$_financialTypes = array();
$query = civicrm_api3('FinancialType', 'get', array( $query = civicrm_api3('FinancialType', 'get', array(
'is_active' => 1,
'option.limit' => 0, 'option.limit' => 0,
'is_active' => 1,
'return' => 'id,name' 'return' => 'id,name'
)); ));
foreach ($query['values'] as $type) { foreach ($query['values'] as $type) {
$financial_types[$type['id']] = $type['name']; static::$_financialTypes[$type['id']] = $type['name'];
} }
return $financial_types; }
return static::$_financialTypes;
} }
/** /**
@ -366,17 +533,19 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
* *
* @throws \CiviCRM_API3_Exception * @throws \CiviCRM_API3_Exception
*/ */
public function getMembershipTypes() { public static function getMembershipTypes() {
$membership_types = array(); if (!isset(static::$_membershipTypes)) {
static::$_membershipTypes = array();
$query = civicrm_api3('MembershipType', 'get', array( $query = civicrm_api3('MembershipType', 'get', array(
'is_active' => 1,
'option.limit' => 0, 'option.limit' => 0,
'is_active' => 1,
'return' => 'id,name' 'return' => 'id,name'
)); ));
foreach ($query['values'] as $type) { foreach ($query['values'] as $type) {
$membership_types[$type['id']] = $type['name']; static::$_membershipTypes[$type['id']] = $type['name'];
} }
return $membership_types; }
return static::$_membershipTypes;
} }
/** /**
@ -387,21 +556,23 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
* *
* @throws \CiviCRM_API3_Exception * @throws \CiviCRM_API3_Exception
*/ */
public function getGenderOptions() { public static function getGenderOptions() {
$genders = array(); if (!isset(static::$_genderOptions)) {
static::$_genderOptions = array();
$query = civicrm_api3('OptionValue', 'get', array( $query = civicrm_api3('OptionValue', 'get', array(
'option.limit' => 0,
'option_group_id' => 'gender', 'option_group_id' => 'gender',
'is_active' => 1, 'is_active' => 1,
'option.limit' => 0,
'return' => array( 'return' => array(
'value', 'value',
'label', 'label',
), ),
)); ));
foreach ($query['values'] as $gender) { foreach ($query['values'] as $gender) {
$genders[$gender['value']] = $gender['label']; static::$_genderOptions[$gender['value']] = $gender['label'];
} }
return $genders; }
return static::$_genderOptions;
} }
/** /**
@ -411,32 +582,36 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
* *
* @throws \CiviCRM_API3_Exception * @throws \CiviCRM_API3_Exception
*/ */
public function getSepaCreditors() { public static function getSepaCreditors() {
$creditors = array(); if (!isset(static::$_sepaCreditors)) {
static::$_sepaCreditors = array();
if (CRM_Twingle_Submission::civiSepaEnabled()) { if (CRM_Twingle_Submission::civiSepaEnabled()) {
$result = civicrm_api3('SepaCreditor', 'get', array( $result = civicrm_api3('SepaCreditor', 'get', array(
'option.limit' => 0, 'option.limit' => 0,
)); ));
foreach ($result['values'] as $sepa_creditor) { foreach ($result['values'] as $sepa_creditor) {
$creditors[$sepa_creditor['id']] = $sepa_creditor['name']; static::$_sepaCreditors[$sepa_creditor['id']] = $sepa_creditor['name'];
} }
} }
}
return $creditors; return static::$_sepaCreditors;
} }
/** /**
* Retrieves payment instruments present within the system as options for * Retrieves payment instruments present within the system as options for
* select form elements. * select form elements.
*
* @return array
*
* @throws \CiviCRM_API3_Exception
*/ */
public function getPaymentInstruments() { public static function getPaymentInstruments() {
if (!isset(self::$_paymentInstruments)) { if (!isset(self::$_paymentInstruments)) {
self::$_paymentInstruments = array(); self::$_paymentInstruments = array();
$query = civicrm_api3('OptionValue', 'get', array( $query = civicrm_api3('OptionValue', 'get', array(
'option.limit' => 0,
'option_group_id' => 'payment_instrument', 'option_group_id' => 'payment_instrument',
'is_active' => 1, 'is_active' => 1,
'option.limit' => 0,
'return' => 'value,label' 'return' => 'value,label'
)); ));
foreach ($query['values'] as $payment_instrument) { foreach ($query['values'] as $payment_instrument) {
@ -471,13 +646,49 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
return self::$_paymentInstruments; return self::$_paymentInstruments;
} }
/**
* Retrieves contribution statuses as options for select form elements.
*
* @return array
*
* @throws \CiviCRM_API3_Exception
*/
public static function getContributionStatusOptions() {
if (!isset(self::$_contributionStatusOptions)) {
$query = civicrm_api3(
'OptionValue',
'get',
array(
'option.limit' => 0,
'option_group_id' => 'contribution_status',
'return' => array(
'value',
'label',
)
)
);
foreach ($query['values'] as $contribution_status) {
self::$_contributionStatusOptions[$contribution_status['value']] = $contribution_status['label'];
}
}
return self::$_contributionStatusOptions;
}
/** /**
* Retrieves active groups used as mailing lists within the system as options * Retrieves active groups used as mailing lists within the system as options
* for select form elements. * for select form elements.
*
* @return array
*
* @throws \CiviCRM_API3_Exception
*/ */
public function getNewsletterGroups() { public static function getNewsletterGroups() {
$groups = array(); if (!isset(static::$_newsletterGroups)) {
static::$_newsletterGroups = array();
$group_types = civicrm_api3('OptionValue', 'get', array( $group_types = civicrm_api3('OptionValue', 'get', array(
'option.limit' => 0,
'option_group_id' => 'group_type', 'option_group_id' => 'group_type',
'name' => CRM_Twingle_Submission::GROUP_TYPE_NEWSLETTER, 'name' => CRM_Twingle_Submission::GROUP_TYPE_NEWSLETTER,
)); ));
@ -490,52 +701,72 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
'return' => 'id,name' 'return' => 'id,name'
)); ));
foreach ($query['values'] as $group) { foreach ($query['values'] as $group) {
$groups[$group['id']] = $group['name']; static::$_newsletterGroups[$group['id']] = $group['name'];
} }
} }
else { else {
$groups[''] = E::ts('No mailing lists available'); static::$_newsletterGroups[''] = E::ts('No mailing lists available');
} }
return $groups; }
return static::$_newsletterGroups;
} }
/** /**
* Retrieves active groups as options for select form elements. * Retrieves active groups as options for select form elements.
*
* @return array
*
* @throws \CiviCRM_API3_Exception
*/ */
public function getGroups() { public static function getGroups() {
$groups = array(); if (!isset(static::$_groups)) {
static::$_groups = array();
$query = civicrm_api3('Group', 'get', array( $query = civicrm_api3('Group', 'get', array(
'is_active' => 1,
'option.limit' => 0, 'option.limit' => 0,
'is_active' => 1,
'return' => 'id,name' 'return' => 'id,name'
)); ));
foreach ($query['values'] as $group) { foreach ($query['values'] as $group) {
$groups[$group['id']] = $group['name']; static::$_groups[$group['id']] = $group['name'];
} }
return $groups; }
return static::$_groups;
} }
/** /**
* Retrieves active groups used as postal mailing lists within the system as * Retrieves active groups used as postal mailing lists within the system as
* options for select form elements. * options for select form elements.
*
* @return array
*
* @throws \CiviCRM_API3_Exception
*/ */
public function getPostinfoGroups() { public static function getPostinfoGroups() {
return $this->getGroups(); return static::getGroups();
} }
/** /**
* Retrieves active groups used as donation receipt requester lists within the * Retrieves active groups used as donation receipt requester lists within the
* system as options for select form elements. * system as options for select form elements.
*
* @return array
*
* @throws \CiviCRM_API3_Exception
*/ */
public function getDonationReceiptGroups() { public static function getDonationReceiptGroups() {
return $this->getGroups(); return static::getGroups();
} }
/** /**
* Retrieves campaigns as options for select elements. * Retrieves campaigns as options for select elements.
*
* @return array
*
* @throws \CiviCRM_API3_Exception
*/ */
public function getCampaigns() { public static function getCampaigns() {
$campaigns = array(); if (!isset(static::$_campaigns)) {
static::$_campaigns = array();
$query = civicrm_api3('Campaign', 'get', array( $query = civicrm_api3('Campaign', 'get', array(
'option.limit' => 0, 'option.limit' => 0,
'return' => array( 'return' => array(
@ -544,9 +775,10 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
) )
)); ));
foreach ($query['values'] as $campaign) { foreach ($query['values'] as $campaign) {
$campaigns[$campaign['id']] = $campaign['title']; static::$_campaigns[$campaign['id']] = $campaign['title'];
} }
return $campaigns; }
return static::$_campaigns;
} }
} }

View file

@ -69,6 +69,21 @@ class CRM_Twingle_Profile {
return in_array($project_id, $project_ids); return in_array($project_id, $project_ids);
} }
/**
* @return array
* The profile's configured custom field mapping
*/
public function getCustomFieldMapping() {
$custom_field_mapping = array();
if (!empty($custom_field_definition = $this->getAttribute('custom_field_mapping'))) {
foreach (preg_split('/\r\n|\r|\n/', $custom_field_definition, -1, PREG_SPLIT_NO_EMPTY) as $custom_field_map) {
list($twingle_field_name, $custom_field_name) = explode("=", $custom_field_map);
$custom_field_mapping[$twingle_field_name] = $custom_field_name;
}
}
return $custom_field_mapping;
}
/** /**
* Retrieves all data attributes of the profile. * Retrieves all data attributes of the profile.
* *
@ -100,15 +115,16 @@ class CRM_Twingle_Profile {
* Retrieves an attribute of the profile. * Retrieves an attribute of the profile.
* *
* @param string $attribute_name * @param string $attribute_name
* @param mixed $default
* *
* @return mixed | NULL * @return mixed | NULL
*/ */
public function getAttribute($attribute_name) { public function getAttribute($attribute_name, $default = NULL) {
if (isset($this->data[$attribute_name])) { if (isset($this->data[$attribute_name])) {
return $this->data[$attribute_name]; return $this->data[$attribute_name];
} }
else { else {
return NULL; return $default;
} }
} }
@ -129,6 +145,21 @@ class CRM_Twingle_Profile {
$this->data[$attribute_name] = $value; $this->data[$attribute_name] = $value;
} }
/**
* Get the CiviCRM transaction ID (to be used in contributions and recurring contributions)
*
* @param $twingle_id string Twingle ID
* @return string CiviCRM transaction ID
*/
public function getTransactionID($twingle_id) {
$prefix = Civi::settings()->get('twingle_prefix');
if (empty($prefix)) {
return $twingle_id;
} else {
return $prefix . $twingle_id;
}
}
/** /**
* Verifies whether the profile is valid (i.e. consistent and not colliding * Verifies whether the profile is valid (i.e. consistent and not colliding
* with other profiles). * with other profiles).
@ -165,23 +196,13 @@ class CRM_Twingle_Profile {
* @return array * @return array
*/ */
public static function allowedAttributes() { public static function allowedAttributes() {
return array( return array_merge(
array(
'selector', 'selector',
'location_type_id', 'location_type_id',
'location_type_id_organisation', 'location_type_id_organisation',
'financial_type_id', 'financial_type_id',
'financial_type_id_recur', 'financial_type_id_recur',
'pi_banktransfer',
'pi_debit_manual',
'pi_debit_automatic',
'pi_creditcard',
'pi_mobilephone_germany',
'pi_paypal',
'pi_sofortueberweisung',
'pi_amazonpay',
'pi_paydirekt',
'pi_applepay',
'pi_googlepay',
'sepa_creditor_id', 'sepa_creditor_id',
'gender_male', 'gender_male',
'gender_female', 'gender_female',
@ -191,7 +212,16 @@ class CRM_Twingle_Profile {
'donation_receipt_groups', 'donation_receipt_groups',
'campaign', 'campaign',
'contribution_source', 'contribution_source',
'custom_field_mapping',
'membership_type_id', 'membership_type_id',
),
// Add payment methods.
array_keys(static::paymentInstruments()),
// Add contribution status for all payment methods.
array_map(function ($attribute) {
return $attribute . '_status';
}, array_keys(static::paymentInstruments()))
); );
} }
@ -251,8 +281,13 @@ class CRM_Twingle_Profile {
'donation_receipt_groups' => NULL, 'donation_receipt_groups' => NULL,
'campaign' => NULL, 'campaign' => NULL,
'contribution_source' => NULL, 'contribution_source' => NULL,
'custom_field_mapping' => NULL,
'membership_type_id' => NULL, 'membership_type_id' => NULL,
)); )
// Add contribution status for all payment methods.
+ array_fill_keys(array_map(function($attribute) {
return $attribute . '_status';
}, array_keys(static::paymentInstruments())), CRM_Twingle_Submission::CONTRIBUTION_STATUS_COMPLETED));
} }
/** /**
@ -267,16 +302,14 @@ class CRM_Twingle_Profile {
public static function getProfileForProject($project_id) { public static function getProfileForProject($project_id) {
$profiles = self::getProfiles(); $profiles = self::getProfiles();
// If none matches, use the default profile.
$profile = $profiles['default'];
foreach ($profiles as $profile) { foreach ($profiles as $profile) {
if ($profile->matches($project_id)) { if ($profile->matches($project_id)) {
break; return $profile;
} }
} }
return $profile; // If none matches, use the default profile.
return $profiles['default'];
} }
/** /**
@ -305,7 +338,7 @@ class CRM_Twingle_Profile {
public static function getProfiles() { public static function getProfiles() {
if (self::$_profiles === NULL) { if (self::$_profiles === NULL) {
self::$_profiles = array(); self::$_profiles = array();
if ($profiles_data = CRM_Core_BAO_Setting::getItem('de.systopia.twingle', 'twingle_profiles')) { if ($profiles_data = Civi::settings()->get('twingle_profiles')) {
foreach ($profiles_data as $profile_name => $profile_data) { foreach ($profiles_data as $profile_name => $profile_data) {
self::$_profiles[$profile_name] = new CRM_Twingle_Profile($profile_name, $profile_data); self::$_profiles[$profile_name] = new CRM_Twingle_Profile($profile_name, $profile_data);
} }
@ -330,6 +363,6 @@ class CRM_Twingle_Profile {
foreach (self::$_profiles as $profile_name => $profile) { foreach (self::$_profiles as $profile_name => $profile) {
$profile_data[$profile_name] = $profile->data; $profile_data[$profile_name] = $profile->data;
} }
CRM_Core_BAO_Setting::setItem((object) $profile_data, 'de.systopia.twingle', 'twingle_profiles'); Civi::settings()->set('twingle_profiles', $profile_data);
} }
} }

View file

@ -27,6 +27,11 @@ class CRM_Twingle_Submission {
*/ */
const GROUP_TYPE_NEWSLETTER = 'Mailing List'; const GROUP_TYPE_NEWSLETTER = 'Mailing List';
/**
* The option value for the contribution type for completed contributions.
*/
const CONTRIBUTION_STATUS_COMPLETED = 'Completed';
/** /**
* The default ID of the "Employer of" relationship type. * The default ID of the "Employer of" relationship type.
*/ */
@ -99,6 +104,16 @@ class CRM_Twingle_Submission {
} }
$params['gender_id'] = $gender_id; $params['gender_id'] = $gender_id;
} }
// Validate custom fields parameter, if given.
if (!empty($params['custom_fields'])) {
if (!is_array($custom_fields = json_decode($params['custom_fields'], TRUE))) {
throw new CiviCRM_API3_Exception(
E::ts('Invalid format for custom fields.'),
'invalid_format'
);
}
}
} }
/** /**
@ -254,10 +269,7 @@ class CRM_Twingle_Submission {
'is_active' => 1, 'is_active' => 1,
)); ));
return return
CRM_Core_BAO_Setting::getItem( Civi::settings()->get('twingle_use_sepa')
'de.systopia.twingle',
'twingle_use_sepa'
)
&& $sepa_extension['count']; && $sepa_extension['count'];
} }

94
CRM/Twingle/Tools.php Normal file
View file

@ -0,0 +1,94 @@
<?php
/*------------------------------------------------------------+
| SYSTOPIA Twingle Integration |
| Copyright (C) 2019 SYSTOPIA |
| Author: B. Endres (endres@systopia.de) |
+-------------------------------------------------------------+
| This program is released as free software under the |
| Affero GPL license. You can redistribute it and/or |
| modify it under the terms of this license which you |
| can read by viewing the included agpl.txt or online |
| at www.gnu.org/licenses/agpl.html. Removal of this |
| copyright header is strictly prohibited without |
| written permission from the original author(s). |
+-------------------------------------------------------------*/
use CRM_Twingle_ExtensionUtil as E;
class CRM_Twingle_Tools {
/**
* This flag can be used to temporarily suspend twingle protection
* @var bool
*/
public static $protection_suspended = FALSE;
/**
* Check if the attempted modification of the recurring contribution is allowed.
* If not, an exception will be raised
*
* @param $recurring_contribution_id int
* @param $change array
* @throws Exception if the change is not allowed
*/
public static function checkRecurringContributionChange($recurring_contribution_id, $change) {
// check if a change to the status is planned
if (empty($change['contribution_status_id'])) return;
// check if the target status is not closed
if (in_array($change['contribution_status_id'], [2,5])) return;
// check if we're suspended
if (self::$protection_suspended) return;
// currently only works with prefixes
$prefix = Civi::settings()->get('twingle_prefix');
if (empty($prefix)) return;
// check if protection is turned on
$protection_on = Civi::settings()->get('twingle_protect_recurring');
if (empty($protection_on)) return;
// load the recurring contribution
$recurring_contribution = civicrm_api3('ContributionRecur', 'getsingle', [
'return' => 'trxn_id,contribution_status_id,payment_instrument_id',
'id' => $recurring_contribution_id]);
// check if this is a SEPA transaction
//if (self::isSDD($recurring_contribution['payment_instrument_id'])) return;
// check if it's really a termination (i.e. current status is 2 or 5)
if (!in_array($recurring_contribution['contribution_status_id'], [2,5])) return;
// check if it's a Twingle contribution
if (substr($recurring_contribution['trxn_id'], 0, strlen($prefix)) == $prefix) {
// this is a Twingle contribution that is about to be terminated
throw new Exception(E::ts("This is a Twingle recurring contribution. It should be terminated through the Twingle interface, otherwise it will still be collected."));
}
}
/**
* Check if the given payment instrument is SEPA
*
* @param $payment_instrument_id string payment instrument
* @return boolean
*/
public static function isSDD($payment_instrument_id) {
static $sepa_payment_instruments = NULL;
if ($sepa_payment_instruments === NULL) {
// init with instrument names
$sepa_payment_instruments = ['FRST', 'RCUR', 'OOFF'];
// lookup and add instrument IDs
$lookup = civicrm_api3('OptionValue', 'get', [
'option_group_id' => 'payment_instrument',
'name' => ['IN' => $sepa_payment_instruments],
'return' => 'value'
]);
foreach ($lookup['values'] as $payment_instrument) {
$sepa_payment_instruments[] = $payment_instrument['value'];
}
}
return in_array($payment_instrument_id, $sepa_payment_instruments);
}
}

View file

@ -143,4 +143,24 @@ class CRM_Twingle_Upgrader extends CRM_Twingle_Upgrader_Base {
return TRUE; return TRUE;
} }
/**
* Convert serialized settings from objects to arrays.
*
* @link https://civicrm.org/advisory/civi-sa-2019-21-poi-saved-search-and-report-instance-apis
*/
public function upgrade_5011() {
// Do not use CRM_Core_BAO::getItem() or Civi::settings()->get().
// Extract and unserialize directly from the database.
$twingle_profiles_query = CRM_Core_DAO::executeQuery("
SELECT `value`
FROM `civicrm_setting`
WHERE `name` = 'twingle_profiles';");
if ($twingle_profiles_query->fetch()) {
$profiles = unserialize($twingle_profiles_query->value);
Civi::settings()->set('twingle_profiles', (array) $profiles);
}
return TRUE;
}
} }

View file

@ -13,6 +13,11 @@ The extension is licensed under
*This section is yet to be completed.* *This section is yet to be completed.*
### Configure Extended Contact Matcher (XCM)
Make sure you use an XCM profile with the option *Match contacts by contact ID*
enabled.
### Configure CiviCRM ### Configure CiviCRM
- Go to the Administration console `/civicrm/admin` - Go to the Administration console `/civicrm/admin`
@ -37,17 +42,21 @@ project ID from any other profile. Therefore the default profile will be used
for all newly created Twingle projects. for all newly created Twingle projects.
| Label | Description | | Label | Description |
|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| |---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Profile name | Internal name, used inside the extension. | | Profile name | Internal name, used inside the extension. |
| Project IDs | Twingle project IDs. Separate multiple IDs with commas. | | Project IDs | Twingle project IDs. Separate multiple IDs with commas. |
| Location type | Specify how the address data sent by the form should be categorised in CiviCRM. The list is based on your CiviCRM configuration. | | Location type | Specify how the address data sent by the form should be categorised in CiviCRM. The list is based on your CiviCRM configuration. |
| Location type for organisations | Specify how the address data sent by the form should be categorised in CiviCRM for organisational donations. The list is based on your CiviCRM configuration. | | Location type for organisations | Specify how the address data sent by the form should be categorised in CiviCRM for organisational donations. The list is based on your CiviCRM configuration. |
| Financial type | Specify which financial type incoming one-time donations should be recorded with in CiviCRM. The list is based on your CiviCRM configuration. | | Financial type | Specify which financial type incoming one-time donations should be recorded with in CiviCRM. The list is based on your CiviCRM configuration. |
| Financial type (recurring) | Specify which financial type incoming recurring donations should be recorded with in CiviCRM. The list is based on your CiviCRM configuration. | | Financial type (recurring) | Specify which financial type incoming recurring donations should be recorded with in CiviCRM. The list is based on your CiviCRM configuration. |
| CiviSEPA creditor | When enabled to integrate with CiviSEPA, specify the CiviSEPA creditor to use. |
| Gender options | Specify which CiviCRM gender option the incoming Twingle gender value should be mapped to. The list is based on your CiviCRM configuration. | | Gender options | Specify which CiviCRM gender option the incoming Twingle gender value should be mapped to. The list is based on your CiviCRM configuration. |
| Record *Payment method* as | Specifiy the payment methods mapping for incoming donations for each Twingle payment method. | | Record *Payment method* as | Specifiy the payment methods mapping for incoming donations for each Twingle payment method. |
| CiviSEPA creditor | When enabled to integrate with CiviSEPA, specify the CiviSEPA creditor to use. |
| Sign up for groups | Whenever the donor checked the newsletter/postal mailing/donation receipt checkbox on the Twingle form, the contact will be added to the groups listed here. | | Sign up for groups | Whenever the donor checked the newsletter/postal mailing/donation receipt checkbox on the Twingle form, the contact will be added to the groups listed here. |
| Assign donation to campaign | The donation will be assigned to the selected campaign. If a campaign ID is being submitted using the `campaign_id` parameter, this setting will be overridden with the submitted value. |
| Create membership of type | A membership of the selected type will be created for the Individual contact. If no membership type is selected, no membership will be created. |
| Contribution source | The configured value will be set as the "Source" field for the contribution. |
| Custom field mapping | Additional field values may be set to CiviCRM custom fields using a mapping. See the option's help text for the exact format. |
## API documentation ## API documentation

View file

@ -80,15 +80,16 @@ function civicrm_api3_twingle_donation_Cancel($params) {
} }
// Retrieve (recurring) contribution. // Retrieve (recurring) contribution.
$default_profile = CRM_Twingle_Profile::getProfile('default');
try { try {
$contribution = civicrm_api3('Contribution', 'getsingle', array( $contribution = civicrm_api3('Contribution', 'getsingle', array(
'trxn_id' => $params['trx_id'], 'trxn_id' => $default_profile->getTransactionID($params['trx_id']),
)); ));
$contribution_type = 'Contribution'; $contribution_type = 'Contribution';
} }
catch (CiviCRM_API3_Exception $exception) { catch (CiviCRM_API3_Exception $exception) {
$contribution = civicrm_api3('ContributionRecur', 'getsingle', array( $contribution = civicrm_api3('ContributionRecur', 'getsingle', array(
'trxn_id' => $params['trx_id'], 'trxn_id' => $default_profile->getTransactionID($params['trx_id']),
)); ));
$contribution_type = 'ContributionRecur'; $contribution_type = 'ContributionRecur';
} }
@ -121,12 +122,14 @@ function civicrm_api3_twingle_donation_Cancel($params) {
)); ));
} }
else { else {
CRM_Twingle_Tools::$protection_suspended = TRUE;
$contribution = civicrm_api3($contribution_type, 'create', array( $contribution = civicrm_api3($contribution_type, 'create', array(
'id' => $contribution['id'], 'id' => $contribution['id'],
'cancel_date' => $params['cancelled_at'], 'cancel_date' => $params['cancelled_at'],
'contribution_status_id' => 'Cancelled', 'contribution_status_id' => 'Cancelled',
'cancel_reason' => $params['cancel_reason'], 'cancel_reason' => $params['cancel_reason'],
)); ));
CRM_Twingle_Tools::$protection_suspended = FALSE;
} }
$result = civicrm_api3_create_success($contribution); $result = civicrm_api3_create_success($contribution);

View file

@ -72,8 +72,9 @@ function civicrm_api3_twingle_donation_endrecurring($params) {
); );
} }
$default_profile = CRM_Twingle_Profile::getProfile('default');
$contribution = civicrm_api3('ContributionRecur', 'getsingle', array( $contribution = civicrm_api3('ContributionRecur', 'getsingle', array(
'trxn_id' => $params['trx_id'], 'trxn_id' => $default_profile->getTransactionID($params['trx_id']),
)); ));
// End SEPA mandate (which ends the associated recurring contribution) or // End SEPA mandate (which ends the associated recurring contribution) or
// recurring contributions. // recurring contributions.
@ -87,6 +88,16 @@ function civicrm_api3_twingle_donation_endrecurring($params) {
time(), time(),
date_create_from_format('Ymd', $params['cancelled_at'])->getTimestamp() date_create_from_format('Ymd', $params['cancelled_at'])->getTimestamp()
)); ));
// verify that the mandate has not been terminated in the past
$mandate_status = civicrm_api3('SepaMandate', 'getvalue', ['return' => 'status', 'id' => $mandate_id]);
if ($mandate_status != 'FRST' && $mandate_status != 'RCUR') {
throw new CiviCRM_API3_Exception(
E::ts("SEPA Mandate [%1] already terminated.", [1 => $mandate_id]),
'api_error'
);
}
if (!CRM_Sepa_BAO_SEPAMandate::terminateMandate( if (!CRM_Sepa_BAO_SEPAMandate::terminateMandate(
$mandate_id, $mandate_id,
$end_date, $end_date,
@ -102,11 +113,13 @@ function civicrm_api3_twingle_donation_endrecurring($params) {
)); ));
} }
else { else {
CRM_Twingle_Tools::$protection_suspended = TRUE;
$contribution = civicrm_api3('ContributionRecur', 'create', array( $contribution = civicrm_api3('ContributionRecur', 'create', array(
'id' => $contribution['id'], 'id' => $contribution['id'],
'end_date' => $params['ended_at'], 'end_date' => $params['ended_at'],
'contribution_status_id' => 'Completed', 'contribution_status_id' => CRM_Twingle_Submission::CONTRIBUTION_STATUS_COMPLETED,
)); ));
CRM_Twingle_Tools::$protection_suspended = FALSE;
} }
$result = civicrm_api3_create_success($contribution); $result = civicrm_api3_create_success($contribution);

View file

@ -238,6 +238,13 @@ function _civicrm_api3_twingle_donation_Submit_spec(&$params) {
'api.required' => 0, 'api.required' => 0,
'description' => E::ts('The CiviCRM ID of a campaign to assign the contribution.'), 'description' => E::ts('The CiviCRM ID of a campaign to assign the contribution.'),
); );
$params['custom_fields'] = array(
'name' => 'custom_fields',
'title' => E::ts('Custom fields'),
'type' => CRM_Utils_Type::T_STRING,
'api.required' => 0,
'description' => E::ts('Additional information for either the contact or the (recurring) contribution.'),
);
} }
/** /**
@ -271,10 +278,10 @@ function civicrm_api3_twingle_donation_Submit($params) {
// Do not process an already existing contribution with the given // Do not process an already existing contribution with the given
// transaction ID. // transaction ID.
$existing_contribution = civicrm_api3('Contribution', 'get', array( $existing_contribution = civicrm_api3('Contribution', 'get', array(
'trxn_id' => $params['trx_id'] 'trxn_id' => $profile->getTransactionID($params['trx_id'])
)); ));
$existing_contribution_recur = civicrm_api3('ContributionRecur', 'get', array( $existing_contribution_recur = civicrm_api3('ContributionRecur', 'get', array(
'trxn_id' => $params['trx_id'] 'trxn_id' => $profile->getTransactionID($params['trx_id'])
)); ));
if ($existing_contribution['count'] > 0 || $existing_contribution_recur['count'] > 0) { if ($existing_contribution['count'] > 0 || $existing_contribution_recur['count'] > 0) {
throw new CiviCRM_API3_Exception( throw new CiviCRM_API3_Exception(
@ -283,6 +290,27 @@ function civicrm_api3_twingle_donation_Submit($params) {
); );
} }
// Extract custom field values using the profile's mapping of Twingle fields
// to CiviCRM custom fields.
$custom_fields = array();
if (!empty($params['custom_fields'])) {
$custom_field_mapping = $profile->getCustomFieldMapping();
foreach (json_decode($params['custom_fields']) as $twingle_field => $value) {
if (isset($custom_field_mapping[$twingle_field])) {
// Get custom field definition to store values by entity the field
// extends.
$custom_field_id = substr($custom_field_mapping[$twingle_field], strlen('custom_'));
$custom_field = civicrm_api3('CustomField', 'getsingle', array(
'id' => $custom_field_id,
// Chain a CustomGroup.getsingle API call.
'api.CustomGroup.getsingle' => array(),
));
$custom_fields[$custom_field['api.CustomGroup.getsingle']['extends']][$custom_field_mapping[$twingle_field]] = $value;
}
}
}
// Create contact(s). // Create contact(s).
if ($params['is_anonymous']) { if ($params['is_anonymous']) {
// Retrieve the ID of the contact to use for anonymous donations defined // Retrieve the ID of the contact to use for anonymous donations defined
@ -349,11 +377,21 @@ function civicrm_api3_twingle_donation_Submit($params) {
'user_email' => 'email', 'user_email' => 'email',
'user_telephone' => 'phone', 'user_telephone' => 'phone',
'user_title' => 'formal_title', 'user_title' => 'formal_title',
'debit_iban' => 'iban',
) as $contact_param => $contact_component) { ) as $contact_param => $contact_component) {
if (!empty($params[$contact_param])) { if (!empty($params[$contact_param])) {
$contact_data[$contact_component] = $params[$contact_param]; $contact_data[$contact_component] = $params[$contact_param];
} }
} }
// Add custom field values.
if (!empty($custom_fields['Contact'])) {
$contact_data += $custom_fields['Contact'];
}
if (!empty($custom_fields['Individual'])) {
$contact_data += $custom_fields['Individual'];
}
if (!$contact_id = CRM_Twingle_Submission::getContact( if (!$contact_id = CRM_Twingle_Submission::getContact(
'Individual', 'Individual',
$contact_data $contact_data
@ -378,6 +416,12 @@ function civicrm_api3_twingle_donation_Submit($params) {
$organisation_data = array( $organisation_data = array(
'organization_name' => $params['organization_name'], 'organization_name' => $params['organization_name'],
); );
// Add custom field values.
if (!empty($custom_fields['Organization'])) {
$organisation_data += $custom_fields['Organization'];
}
if (!empty($submitted_address)) { if (!empty($submitted_address)) {
$organisation_data += $submitted_address; $organisation_data += $submitted_address;
// Use configured location type for organisation address. // Use configured location type for organisation address.
@ -467,12 +511,17 @@ function civicrm_api3_twingle_donation_Submit($params) {
$contribution_data = array( $contribution_data = array(
'contact_id' => (isset($organisation_id) ? $organisation_id : $contact_id), 'contact_id' => (isset($organisation_id) ? $organisation_id : $contact_id),
'currency' => $params['currency'], 'currency' => $params['currency'],
'trxn_id' => $params['trx_id'], 'trxn_id' => $profile->getTransactionID($params['trx_id']),
'payment_instrument_id' => $params['payment_instrument_id'], 'payment_instrument_id' => $params['payment_instrument_id'],
'amount' => $params['amount'] / 100, 'amount' => $params['amount'] / 100,
'total_amount' => $params['amount'] / 100, 'total_amount' => $params['amount'] / 100,
); );
// Add custom field values.
if (!empty($custom_fields['Contribution'])) {
$contribution_data += $custom_fields['Contribution'];
}
if (!empty($params['purpose'])) { if (!empty($params['purpose'])) {
$contribution_data['note'] = $params['purpose']; $contribution_data['note'] = $params['purpose'];
} }
@ -526,6 +575,10 @@ function civicrm_api3_twingle_donation_Submit($params) {
) )
// ... and frequency unit and interval from a static mapping. // ... and frequency unit and interval from a static mapping.
+ CRM_Twingle_Submission::getFrequencyMapping($params['donation_rhythm']); + CRM_Twingle_Submission::getFrequencyMapping($params['donation_rhythm']);
// Add custom field values.
if (!empty($custom_fields['ContributionRecur'])) {
$mandate_data += $custom_fields['ContributionRecur'];
}
// Add cycle day for recurring contributions. // Add cycle day for recurring contributions.
if ($params['donation_rhythm'] != 'one_time') { if ($params['donation_rhythm'] != 'one_time') {
@ -557,6 +610,13 @@ function civicrm_api3_twingle_donation_Submit($params) {
'financial_type_id' => $profile->getAttribute('financial_type_id_recur'), 'financial_type_id' => $profile->getAttribute('financial_type_id_recur'),
) )
+ CRM_Twingle_Submission::getFrequencyMapping($params['donation_rhythm']); + CRM_Twingle_Submission::getFrequencyMapping($params['donation_rhythm']);
// Add custom field values.
if (!empty($custom_fields['ContributionRecur'])) {
$contribution_recur_data += $custom_fields['ContributionRecur'];
$contribution_data += $custom_fields['ContributionRecur'];
}
$contribution_recur = civicrm_api3('contributionRecur', 'create', $contribution_recur_data); $contribution_recur = civicrm_api3('contributionRecur', 'create', $contribution_recur_data);
if ($contribution_recur['is_error']) { if ($contribution_recur['is_error']) {
throw new CiviCRM_API3_Exception( throw new CiviCRM_API3_Exception(
@ -573,7 +633,7 @@ function civicrm_api3_twingle_donation_Submit($params) {
// Create contribution. // Create contribution.
$contribution_data += array( $contribution_data += array(
'contribution_status_id' => 'Completed', 'contribution_status_id' => $profile->getAttribute("pi_{$params['payment_method']}_status", CRM_Twingle_Submission::CONTRIBUTION_STATUS_COMPLETED),
'receive_date' => $params['confirmed_at'], 'receive_date' => $params['confirmed_at'],
); );
$contribution = civicrm_api3('Contribution', 'create', $contribution_data); $contribution = civicrm_api3('Contribution', 'create', $contribution_data);

View file

@ -19,6 +19,7 @@
<develStage>dev</develStage> <develStage>dev</develStage>
<compatibility> <compatibility>
<ver>4.7</ver> <ver>4.7</ver>
<ver>5.0</ver>
</compatibility> </compatibility>
<comments></comments> <comments></comments>
<requires> <requires>

View file

@ -31,4 +31,32 @@ return array(
'is_contact' => 0, 'is_contact' => 0,
'description' => 'Whether to provide CiviSEPA functionality for manual debit payment method. This requires the CiviSEPA (org.project60.sepa) extension be installed.', 'description' => 'Whether to provide CiviSEPA functionality for manual debit payment method. This requires the CiviSEPA (org.project60.sepa) extension be installed.',
), ),
'twingle_protect_recurring' => array(
'group_name' => 'de.systopia.twingle',
'group' => 'de.systopia.twingle',
'name' => 'twingle_protect_recurring',
'type' => 'Boolean',
'quick_form_type' => 'YesNo',
'html_type' => 'radio',
'title' => 'Protect Recurring Contributions',
'default' => 0,
'add' => '4.6',
'is_domain' => 1,
'is_contact' => 0,
'description' => 'Will protect all recurring contributions created by Twingle from termination, since this does NOT terminate the Twingle collection process. Currently only works with a prefix',
),
'twingle_prefix' => array(
'group_name' => 'de.systopia.twingle',
'group' => 'de.systopia.twingle',
'name' => 'twingle_prefix',
'type' => CRM_Utils_Type::T_STRING,
'quick_form_type' => 'Element',
'html_type' => 'text',
'title' => 'Twingle ID Prefix',
'default' => '',
'add' => '4.6',
'is_domain' => 1,
'is_contact' => 0,
'description' => 'You can use this setting to add a prefix to the Twingle transaction ID, in order to avoid collisions with other transaction ids.',
),
); );

View file

@ -16,6 +16,11 @@
{ts domain="de.systopia.twingle"}Select which location type to use for addresses for individuals, either when no organisation name is specified, or an organisation address can not be shared with the individual contact.{/ts} {ts domain="de.systopia.twingle"}Select which location type to use for addresses for individuals, either when no organisation name is specified, or an organisation address can not be shared with the individual contact.{/ts}
{/htxt} {/htxt}
{htxt id='id-project_ids'}
{ts domain="de.systopia.twingle"}Put your project's Twingle ID in here, to activate this profile for that project.{/ts}
{ts domain="de.systopia.twingle"}You can also provide multiple project IDs separated by a comma.{/ts}
{/htxt}
{htxt id='id-location_type_id_organisation'} {htxt id='id-location_type_id_organisation'}
{ts domain="de.systopia.twingle"}Select which location type to use for addresses for organisations and shared organisation addresses for individual contacts.{/ts} {ts domain="de.systopia.twingle"}Select which location type to use for addresses for organisations and shared organisation addresses for individual contacts.{/ts}
{/htxt} {/htxt}
@ -27,3 +32,17 @@
{htxt id='id-financial_type_id_recur'} {htxt id='id-financial_type_id_recur'}
{ts domain="de.systopia.twingle"}Select which financial type to use for recurring contributions.{/ts} {ts domain="de.systopia.twingle"}Select which financial type to use for recurring contributions.{/ts}
{/htxt} {/htxt}
{htxt id='id-custom_field_mapping'}
{ts domain="de.systopia.twingle"}<p>Map Twingle custom fields to CiviCRM custom fields using the following format (each assignment in a separate line):</p>
<pre>twingle_field_1=custom_123<br />twingle_field_2=custom_789</pre>
<p>Always use the <code>custom_[id]</code> notation for CiviCRM custom fields.</p>
<p>Only custom fields extending one of the following CiviCRM entities are allowed:</p>
<ul>
<li><strong>Contact</strong> &ndash; Will be set on the Individual contact</li>
<li><strong>Individual</strong> &ndash; Will be set on the Individual contact</li>
<li><strong>Organization</strong> &ndash; Will be set on the Organization contact, if an organisation name was submitted</li>
<li><strong>Contribution</strong> &ndash; Will be set on the contribution</li>
<li><strong>ContributionRecur</strong> &ndash; Will be set on the recurring contribution and deriving single contributions</li>
</ul>{/ts}
{/htxt}

View file

@ -28,7 +28,23 @@
</tr> </tr>
<tr class="crm-section"> <tr class="crm-section">
<td class="label">{$form.selector.label}</td> <td class="label">{$form.selector.label}
<a
onclick='
CRM.help(
"{ts domain="de.systopia.twingle"}Project IDs{/ts}",
{literal}{
"id": "id-project_ids",
"file": "CRM\/Twingle\/Form\/Profile"
}{/literal}
);
return false;
'
href="#"
title="{ts domain="de.systopia.twingle"}Help{/ts}"
class="helpicon"
></a>
</td>
<td class="content">{$form.selector.html}</td> <td class="content">{$form.selector.html}</td>
</tr> </tr>
@ -151,8 +167,14 @@
<table class="form-layout-compressed"> <table class="form-layout-compressed">
{foreach key=pi_name item=pi_label from=$payment_instruments} {foreach key=pi_name item=pi_label from=$payment_instruments}
<tr class="crm-section {cycle values="odd,even"}"> <tr class="crm-section {cycle values="odd,even"}">
<td class="label">{$form.$pi_name.label}</td> <td class="label">{$form.$pi_name.label}</td>
<td class="content">{$form.$pi_name.html}</td> <td class="content">{$form.$pi_name.html}</td>
{capture assign="pi_contribution_status"}{$pi_name}_status{/capture}
<td class="label">{$form.$pi_contribution_status.label}</td>
<td class="content">{$form.$pi_contribution_status.html}</td>
</tr> </tr>
{/foreach} {/foreach}
</table> </table>
@ -161,7 +183,7 @@
<fieldset> <fieldset>
<legend>{ts domain="de.systopia.twingle"}Groups{/ts}</legend> <legend>{ts domain="de.systopia.twingle"}Groups and Correlations{/ts}</legend>
<table class="form-layout-compressed"> <table class="form-layout-compressed">
@ -195,6 +217,28 @@
<td class="content">{$form.contribution_source.html}</td> <td class="content">{$form.contribution_source.html}</td>
</tr> </tr>
<tr class="crm-section">
<td class="label">
{$form.custom_field_mapping.label}
<a
onclick='
CRM.help(
"{ts domain="de.systopia.twingle"}Custom field mapping{/ts}",
{literal}{
"id": "id-custom_field_mapping",
"file": "CRM\/Twingle\/Form\/Profile"
}{/literal}
);
return false;
'
href="#"
title="{ts domain="de.systopia.twingle"}Help{/ts}"
class="helpicon"
></a>
</td>
<td class="content">{$form.custom_field_mapping.html}</td>
</tr>
</table> </table>
</fieldset> </fieldset>

View file

@ -15,3 +15,11 @@
{htxt id='id-twingle_use_sepa'} {htxt id='id-twingle_use_sepa'}
{ts domain="de.systopia.twingle" 1="<a href=\"https://github.com/project60/org.project60.sepa\" target=\"_blank\">CiviSEPA (<kbd>org.project60.sepa</kbd>) extension</a>"}When the %1 is enabled and one of its payment instruments is assigned to a Twingle payment method (practically the <em>debit_manual</em> payment method), submitting a Twingle donation through the API will create a SEPA mandate with the given data.{/ts} {ts domain="de.systopia.twingle" 1="<a href=\"https://github.com/project60/org.project60.sepa\" target=\"_blank\">CiviSEPA (<kbd>org.project60.sepa</kbd>) extension</a>"}When the %1 is enabled and one of its payment instruments is assigned to a Twingle payment method (practically the <em>debit_manual</em> payment method), submitting a Twingle donation through the API will create a SEPA mandate with the given data.{/ts}
{/htxt} {/htxt}
{htxt id='id-twingle_protect_recurring'}
{ts domain="de.systopia.twingle"}Will protect all recurring contributions created by Twingle from termination, since this does NOT terminate the Twingle collection process{/ts}
{/htxt}
{htxt id='id-twingle_prefix'}
{ts domain="de.systopia.twingle"}You can use this setting to add a prefix to the Twingle transaction ID, in order to avoid collisions with other transaction ids.{/ts}
{/htxt}

View file

@ -39,6 +39,7 @@
</td> </td>
<td> <td>
<a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=edit&name=$profile_name"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Edit profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Edit{/ts}</a> <a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=edit&name=$profile_name"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Edit profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Edit{/ts}</a>
<a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=copy&name=$profile_name"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Copy profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Copy{/ts}</a>
{if $profile_name == 'default'} {if $profile_name == 'default'}
<a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=delete&name=$profile_name"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Reset profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Reset{/ts}</a> <a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=delete&name=$profile_name"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Reset profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Reset{/ts}</a>
{else} {else}

View file

@ -3,6 +3,15 @@
require_once 'twingle.civix.php'; require_once 'twingle.civix.php';
use CRM_Twingle_ExtensionUtil as E; use CRM_Twingle_ExtensionUtil as E;
/**
* Implements hook_civicrm_pre().
*/
function twingle_civicrm_pre($op, $objectName, $id, &$params) {
if ($objectName == 'ContributionRecur' && $op == 'edit') {
CRM_Twingle_Tools::checkRecurringContributionChange($id, $params);
}
}
/** /**
* Implements hook_civicrm_config(). * Implements hook_civicrm_config().
* *