Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement Twingle's double opt-in #35

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CRM/Twingle/Form/Profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,13 @@ public function buildQuickForm() {
array('class' => 'crm-select2 huge', 'multiple' => 'multiple')
);

$this->add(
'checkbox', // field type
'double_opt_in', // field name
E::ts('Use Twingle\'s Double-Opt-In procedure'), // field label
FALSE // is not required
);

$this->add(
'select', // field type
'postinfo_groups', // field name
Expand Down
2 changes: 2 additions & 0 deletions CRM/Twingle/Profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ public static function allowedAttributes() {
'membership_type_id',
'membership_type_id_recur',
'membership_postprocess_call',
'double_opt_in'
),
// Add payment methods.
array_keys(static::paymentInstruments()),
Expand Down Expand Up @@ -293,6 +294,7 @@ public static function createDefaultProfile($name = 'default') {
'custom_field_mapping' => NULL,
'membership_type_id' => NULL,
'membership_type_id_recur' => NULL,
'double_opt_in' => NULL,
)
// Add contribution status for all payment methods.
+ array_fill_keys(array_map(function($attribute) {
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ for all newly created Twingle projects.
| 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. |
| Record *Payment method* as | Specifiy the payment methods mapping for incoming donations for each Twingle payment method. |
| Double-Opt-In | Group membership for newsletter mailing lists will be pending until receivement of confirming API call. Usage in combination with activated Double-Opt-In in Twingle manager. |
| 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 for incoming one-time donations. If no membership type is selected, no membership will be created. |
Expand Down Expand Up @@ -148,3 +149,19 @@ The action accepts the following parameters:
You may also refer to
[the code](https://github.com/systopia/de.systopia.twingle/blob/master/api/v3/TwingleDonation/Cancel.php)
for more insight into this API action.

### Double-Opt-In confirmation

- Entity: `TwingleDonation`
- Action: `doubleoptinconfirm`

The action accepts the following parameters:

| Parameter | Type | Description | Values/Format | Required |
|------------------------------|--------|----------------------------------------------------|-------------------------------------------------------|----------|
| <nobr>`project_id`</nobr> | String | The Twingle project ID | | Yes |
| <nobr>`user_email`</nobr> | String | The e-mail address of the contact | A valid e-mail address | Yes |

You may also refer to
[the code](https://github.com/systopia/de.systopia.twingle/blob/master/api/v3/TwingleDonation/Doubleoptinconfirm.php)
for more insight into this API action.
115 changes: 115 additions & 0 deletions api/v3/TwingleDonation/Doubleoptinconfirm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php
/*------------------------------------------------------------+
| SYSTOPIA Twingle Integration |
| Copyright (C) 2018 SYSTOPIA |
| Author: J. Schuppe (schuppe@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;

/**
* TwingleDonation.DoubleOptInConfirm API specification (optional)
* This is used for documentation and validation.
*
* @param array $params description of fields supported by this API call
*
* @return void
*
* @see http://wiki.civicrm.org/confluence/display/CRMDOC/API+Architecture+Standards
*/
function _civicrm_api3_twingle_donation_DoubleOptInConfirm_spec(&$params)
{
$params['project_id'] = array(
'name' => 'project_id',
'title' => E::ts('Project ID'),
'type' => CRM_Utils_Type::T_STRING,
'api.required' => 1,
'description' => E::ts('The Twingle project ID.'),
);
$params['user_email'] = array(
'name' => 'user_email',
'title' => E::ts('Email address'),
'type' => CRM_Utils_Type::T_STRING,
'api.required' => 1,
'description' => E::ts('The e-mail address of the contact.')
);
}

/**
* TwingleDonation.Cancel API
*
* @param array $params
* @return array API result descriptor
* @see civicrm_api3_create_success
* @see civicrm_api3_create_error
*/
function civicrm_api3_twingle_donation_DoubleOptInConfirm($params)
{
// Log call if debugging is enabled within civicrm.settings.php.
if (defined('TWINGLE_API_LOGGING') && TWINGLE_API_LOGGING) {
CRM_Core_Error::debug_log_message('TwingleDonation.DoubleOptInConfirm: ' . json_encode($params, JSON_PRETTY_PRINT));
}

try {
// Get the profile defined for the given form ID, or the default profile
// if none matches.
$profile = CRM_Twingle_Profile::getProfileForProject($params['project_id']);

// Get the newsletter groups defined in the profile
$newsletter_groups = $profile->getAttribute('newsletter_groups');

// Extract user email from API call
if (!empty($params['user_email'])) {
$contacts = civicrm_api3('Email', 'get', array(
'seqential' => 1,
'email' => $params['user_email'],
));

// Get pending group memberships for user
if (!empty($contacts['values'])) {
foreach ($contacts['values'] as $contact) {
$groups = civicrm_api3('GroupContact', 'get', array(
'sequential' => 1,
'contact_id' => $contact['contact_id'],
'status' => "Pending",
));

// Only in newsletter groups: change group membership from pending to added
if (!empty($groups['values'])) {
foreach ($groups['values'] as $group) {
if (in_array($group['group_id'], $newsletter_groups)) {
civicrm_api3('GroupContact', 'create', array(
'group_id' => $group['group_id'],
'contact_id' => $contact['contact_id'],
'status' => "Added",
$result_values['groups'][] = $group['group_id'],
));
// Display message if group membership was confirmed correctly
$result_values['double_opt_in'][$group['group_id']] = "Subscription confirmed";
}
}
// Display message if there is no pending group membership
} else {
$result_values['double_opt_in'][] = "Could not confirm subscription: No pending group membership";
}
}
// Display message if email can't be found
} else {
$result_values['double_opt_in'][] = "Could not confirm subscription: Email not found";
}
}
Comment on lines +70 to +108
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contact should be identified using XCM, just as the submit API action does. Otherwise, this might alter group memberships for other contacts with the same e-mail address. We should also only target that one single contact identified by XCM with the same profile.

Therefore, all user_ parameters (and maybe custom_fields also) should be added to this API action.

Copy link
Contributor Author

@MarcMichalsky MarcMichalsky Jun 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering that a Double-Opt-In confirmation always refers to an email address, I thought that matching a contact wouldn't be necessary. Especially when the contact has already been matched in the submit step. How should we keep DOI confirmations from different contacts with the same email apart, anyway?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what XCM takes care of: either match a single contact or create one, according to the XCM profile configuration being used. I.e. contacts with the same e-mail address don't matter, since XCM will only return one contact ID. Since GroupContact is a thing that belongs to a contact (not an e-mail entity) - it's a crosstab with group_id and contact_id - we should really make sure we select the correct one and thus identify the previously matched contact again using XCM.

$result = civicrm_api3_create_success($result_values);
} catch (Exception $exception) {
$result = civicrm_api3_create_error($exception->getMessage());
}

return $result;
}
22 changes: 21 additions & 1 deletion api/v3/TwingleDonation/Submit.php
Original file line number Diff line number Diff line change
Expand Up @@ -484,8 +484,28 @@ function civicrm_api3_twingle_donation_Submit($params) {
$result_values['organization'] = $organisation_id;
}

// If Twingle's Double-Opt-In procedure is used, add contact with status "pending" to the newsletter groups
// defined in the profile
if (!empty($profile->getAttribute('double_opt_in')) &&
!empty($params['newsletter']) &&
!empty($groups = $profile->getAttribute('newsletter_groups'))) {
foreach ($groups as $group_id) {
if (empty(civicrm_api3('GroupContact', 'get', array(
'group_id' => $group_id,
'contact_id' => $contact_id,
))['values'])) {
civicrm_api3('GroupContact', 'create', array(
'group_id' => $group_id,
'contact_id' => $contact_id,
'status' => "Pending",
));
$result_values['double_opt_in'][] = "Pending";
}
Comment on lines +493 to +503
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make the GroupContact.get a GroupContact.getsingle wrapped with a try and put the GroupContact.create in the catch? This would avoid "Undefined index" warnings when something goes wrong with the API call. There can only be one GroupContact record per contact_id-group_id combination.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea!

$result_values['newsletter'][] = $group_id;
}

// If requested, add contact to newsletter groups defined in the profile.
if (!empty($params['newsletter']) && !empty($groups = $profile->getAttribute('newsletter_groups'))) {
} elseif (!empty($params['newsletter']) && !empty($groups = $profile->getAttribute('newsletter_groups'))) {
foreach ($groups as $group_id) {
civicrm_api3('GroupContact', 'create', array(
'group_id' => $group_id,
Expand Down
4 changes: 4 additions & 0 deletions l10n/de_DE/LC_MESSAGES/twingle.po
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,7 @@ msgstr "Profil %1 zurücksetzen"
#: templates/CRM/Twingle/Page/Profiles.tpl
msgid "Delete profile %1"
msgstr "Profil % 1 löschen"

#: templates/CRM/Twingle/Page/Profiles.tpl
msgid "Use Twingle's Double-Opt-In procedure"
msgstr "Nutze Twingles Double-Opt-In-Verfahren"
5 changes: 5 additions & 0 deletions templates/CRM/Twingle/Form/Profile.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@

<table class="form-layout-compressed">

<tr class="crm-section">
<td class="label">{$form.double_opt_in.label}</td>
<td class="content">{$form.double_opt_in.html}</td>
</tr>

<tr class="crm-section">
<td class="label">{$form.newsletter_groups.label}</td>
<td class="content">{$form.newsletter_groups.html}</td>
Expand Down
1 change: 1 addition & 0 deletions twingle.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ function twingle_civicrm_alterAPIPermissions($entity, $action, &$params, &$permi
$permissions['twingle_donation']['submit'] = array('access Twingle API');
$permissions['twingle_donation']['cancel'] = array('access Twingle API');
$permissions['twingle_donation']['endrecurring'] = array('access Twingle API');
$permissions['twingle_donation']['doubleoptinconfirm'] = array('access Twingle API');
}

// --- Functions below this ship commented out. Uncomment as required. ---
Expand Down