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)) {
$query = civicrm_api3('LocationType', 'get', array( static::$_locationTypes = array();
'is_active' => 1, $query = civicrm_api3('LocationType', 'get', array(
)); 'option.limit' => 0,
foreach ($query['values'] as $type) { 'is_active' => 1,
$location_types[$type['id']] = $type['name']; ));
foreach ($query['values'] as $type) {
static::$_locationTypes[$type['id']] = $type['name'];
}
} }
return static::$_locationTypes;
return $location_types;
} }
/** /**
@ -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)) {
$query = civicrm_api3('FinancialType', 'get', array( static::$_financialTypes = array();
'is_active' => 1, $query = civicrm_api3('FinancialType', 'get', array(
'option.limit' => 0, 'option.limit' => 0,
'return' => 'id,name' 'is_active' => 1,
)); 'return' => 'id,name'
foreach ($query['values'] as $type) { ));
$financial_types[$type['id']] = $type['name']; foreach ($query['values'] as $type) {
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)) {
$query = civicrm_api3('MembershipType', 'get', array( static::$_membershipTypes = array();
'is_active' => 1, $query = civicrm_api3('MembershipType', 'get', array(
'option.limit' => 0, 'option.limit' => 0,
'return' => 'id,name' 'is_active' => 1,
)); 'return' => 'id,name'
foreach ($query['values'] as $type) { ));
$membership_types[$type['id']] = $type['name']; foreach ($query['values'] as $type) {
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)) {
$query = civicrm_api3('OptionValue', 'get', array( static::$_genderOptions = array();
'option_group_id' => 'gender', $query = civicrm_api3('OptionValue', 'get', array(
'is_active' => 1, 'option.limit' => 0,
'option.limit' => 0, 'option_group_id' => 'gender',
'return' => array( 'is_active' => 1,
'value', 'return' => array(
'label', 'value',
), 'label',
)); ),
foreach ($query['values'] as $gender) { ));
$genders[$gender['value']] = $gender['label']; foreach ($query['values'] as $gender) {
static::$_genderOptions[$gender['value']] = $gender['label'];
}
} }
return $genders; return static::$_genderOptions;
} }
/** /**
@ -411,33 +582,37 @@ 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 static::$_sepaCreditors;
return $creditors;
} }
/** /**
* 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) {
// Do not include CiviSEPA payment instruments, but add a SEPA option if // Do not include CiviSEPA payment instruments, but add a SEPA option if
@ -472,81 +647,138 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
} }
/** /**
* Retrieves active groups used as mailing lists within the system as options * Retrieves contribution statuses as options for select form elements.
* for select form elements. *
* @return array
*
* @throws \CiviCRM_API3_Exception
*/ */
public function getNewsletterGroups() { public static function getContributionStatusOptions() {
$groups = array(); if (!isset(self::$_contributionStatusOptions)) {
$group_types = civicrm_api3('OptionValue', 'get', array( $query = civicrm_api3(
'option_group_id' => 'group_type', 'OptionValue',
'name' => CRM_Twingle_Submission::GROUP_TYPE_NEWSLETTER, 'get',
)); array(
if ($group_types['count'] > 0) { 'option.limit' => 0,
$group_type = reset($group_types['values']); 'option_group_id' => 'contribution_status',
$query = civicrm_api3('Group', 'get', array( 'return' => array(
'is_active' => 1, 'value',
'group_type' => array('LIKE' => '%' . CRM_Utils_Array::implodePadded($group_type['value']) . '%'), 'label',
'option.limit' => 0, )
'return' => 'id,name' )
)); );
foreach ($query['values'] as $group) {
$groups[$group['id']] = $group['name']; foreach ($query['values'] as $contribution_status) {
self::$_contributionStatusOptions[$contribution_status['value']] = $contribution_status['label'];
} }
} }
else {
$groups[''] = E::ts('No mailing lists available'); return self::$_contributionStatusOptions;
}
/**
* Retrieves active groups used as mailing lists within the system as options
* for select form elements.
*
* @return array
*
* @throws \CiviCRM_API3_Exception
*/
public static function getNewsletterGroups() {
if (!isset(static::$_newsletterGroups)) {
static::$_newsletterGroups = array();
$group_types = civicrm_api3('OptionValue', 'get', array(
'option.limit' => 0,
'option_group_id' => 'group_type',
'name' => CRM_Twingle_Submission::GROUP_TYPE_NEWSLETTER,
));
if ($group_types['count'] > 0) {
$group_type = reset($group_types['values']);
$query = civicrm_api3('Group', 'get', array(
'is_active' => 1,
'group_type' => array('LIKE' => '%' . CRM_Utils_Array::implodePadded($group_type['value']) . '%'),
'option.limit' => 0,
'return' => 'id,name'
));
foreach ($query['values'] as $group) {
static::$_newsletterGroups[$group['id']] = $group['name'];
}
}
else {
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)) {
$query = civicrm_api3('Group', 'get', array( static::$_groups = array();
'is_active' => 1, $query = civicrm_api3('Group', 'get', array(
'option.limit' => 0, 'option.limit' => 0,
'return' => 'id,name' 'is_active' => 1,
)); 'return' => 'id,name'
foreach ($query['values'] as $group) { ));
$groups[$group['id']] = $group['name']; foreach ($query['values'] as $group) {
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)) {
$query = civicrm_api3('Campaign', 'get', array( static::$_campaigns = array();
'option.limit' => 0, $query = civicrm_api3('Campaign', 'get', array(
'return' => array( 'option.limit' => 0,
'id', 'return' => array(
'title', 'id',
) 'title',
)); )
foreach ($query['values'] as $campaign) { ));
$campaigns[$campaign['id']] = $campaign['title']; foreach ($query['values'] as $campaign) {
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,33 +196,32 @@ class CRM_Twingle_Profile {
* @return array * @return array
*/ */
public static function allowedAttributes() { public static function allowedAttributes() {
return array( return array_merge(
'selector', array(
'location_type_id', 'selector',
'location_type_id_organisation', 'location_type_id',
'financial_type_id', 'location_type_id_organisation',
'financial_type_id_recur', 'financial_type_id',
'pi_banktransfer', 'financial_type_id_recur',
'pi_debit_manual', 'sepa_creditor_id',
'pi_debit_automatic', 'gender_male',
'pi_creditcard', 'gender_female',
'pi_mobilephone_germany', 'gender_other',
'pi_paypal', 'newsletter_groups',
'pi_sofortueberweisung', 'postinfo_groups',
'pi_amazonpay', 'donation_receipt_groups',
'pi_paydirekt', 'campaign',
'pi_applepay', 'contribution_source',
'pi_googlepay', 'custom_field_mapping',
'sepa_creditor_id', 'membership_type_id',
'gender_male', ),
'gender_female', // Add payment methods.
'gender_other', array_keys(static::paymentInstruments()),
'newsletter_groups',
'postinfo_groups', // Add contribution status for all payment methods.
'donation_receipt_groups', array_map(function ($attribute) {
'campaign', return $attribute . '_status';
'contribution_source', }, array_keys(static::paymentInstruments()))
'membership_type_id',
); );
} }
@ -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`
@ -36,18 +41,22 @@ The *default* profile is used whenever the plugin cannot match the Twingle
project ID from any other profile. Therefore the default profile will be used 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. |
| Gender options | Specify which CiviCRM gender option the incoming Twingle gender value should be mapped to. The list is based on your CiviCRM configuration. | | CiviSEPA creditor | When enabled to integrate with CiviSEPA, specify the CiviSEPA creditor to use. |
| Record *Payment method* as | Specifiy the payment methods mapping for incoming donations for each Twingle payment method. | | Gender options | Specify which CiviCRM gender option the incoming Twingle gender value should be mapped to. The list is based on your CiviCRM configuration. |
| CiviSEPA creditor | When enabled to integrate with CiviSEPA, specify the CiviSEPA creditor to use. | | Record *Payment method* as | Specifiy the payment methods mapping for incoming donations for each Twingle payment method. |
| 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().
* *