[WIP] TwingleDonation.Submit API action

This commit is contained in:
Jens Schuppe 2018-10-04 15:59:35 +02:00
parent f8575ac7ce
commit 061972706c
4 changed files with 978 additions and 0 deletions

278
CRM/Twingle/Profile.php Normal file
View file

@ -0,0 +1,278 @@
<?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;
/**
* Profiles define how incoming submissions from the Twingle API are
* processed in CiviCRM.
*/
class CRM_Twingle_Profile {
/**
* @var CRM_Twingle_Profile[] $_profiles
* Caches the profile objects.
*/
protected static $_profiles = NULL;
/**
* @var string $name
* The name of the profile.
*/
protected $name = NULL;
/**
* @var array $data
* The properties of the profile.
*/
protected $data = NULL;
/**
* CRM_Twingle_Profile constructor.
*
* @param string $name
* The name of the profile.
* @param array $data
* The properties of the profile
*/
public function __construct($name, $data) {
$this->name = $name;
$allowed_attributes = self::allowedAttributes();
$this->data = $data + array_combine(
$allowed_attributes,
array_fill(0, count($allowed_attributes), NULL)
);
}
/**
* Checks whether the profile's selector matches the given project ID.
*
* @param string | int $project_id
*
* @return bool
*/
public function matches($project_id) {
$selector = $this->getAttribute('selector');
$project_ids = explode(',', $selector);
return in_array($project_id, $project_ids);
}
/**
* Retrieves all data attributes of the profile.
*
* @return array
*/
public function getData() {
return $this->data;
}
/**
* Retrieves the profile name.
*
* @return string
*/
public function getName() {
return $this->name;
}
/**
* Sets the profile name.
*
* @param $name
*/
public function setName($name) {
$this->name = $name;
}
/**
* Retrieves an attribute of the profile.
*
* @param string $attribute_name
*
* @return mixed | NULL
*/
public function getAttribute($attribute_name) {
if (isset($this->data[$attribute_name])) {
return $this->data[$attribute_name];
}
else {
return NULL;
}
}
/**
* Sets an attribute of the profile.
*
* @param string $attribute_name
* @param mixed $value
*
* @throws \Exception
* When the attribute name is not known.
*/
public function setAttribute($attribute_name, $value) {
if (!in_array($attribute_name, self::allowedAttributes())) {
throw new Exception(E::ts('Unknown attribute %1.', array(1 => $attribute_name)));
}
// TODO: Check if value is acceptable.
$this->data[$attribute_name] = $value;
}
/**
* Verifies whether the profile is valid (i.e. consistent and not colliding
* with other profiles).
*
* @throws Exception
* When the profile could not be successfully validated.
*/
public function verifyProfile() {
// TODO: check
// data of this profile consistent?
// conflicts with other profiles?
}
/**
* Persists the profile within the CiviCRM settings.
*/
public function saveProfile() {
self::$_profiles[$this->getName()] = $this;
$this->verifyProfile();
self::storeProfiles();
}
/**
* Deletes the profile from the CiviCRM settings.
*/
public function deleteProfile() {
unset(self::$_profiles[$this->getName()]);
self::storeProfiles();
}
/**
* Returns an array of attributes allowed for a profile.
*
* @return array
*/
public static function allowedAttributes() {
// TODO: Adjust attributes for Twingle.
return array(
'selector',
'location_type_id',
'financial_type_id',
'campaign_id',
'pi_creditcard',
'pi_sepa',
'pi_paypal',
'groups',
);
}
/**
* Returns the default profile with "factory" defaults.
*
* @param string $name
* The profile name. Defaults to "default".
*
* @return CRM_Twingle_Profile
*/
public static function createDefaultProfile($name = 'default') {
// TODO: Adjust attributes for Twingle.
return new CRM_Twingle_Profile($name, array(
'selector' => '',
'location_type_id' => CRM_Twingle_Submission::LOCATION_TYPE_ID_WORK,
'financial_type_id' => 1, // "Donation"
'campaign_id' => '',
'pi_creditcard' => 1, // "Credit Card"
'pi_sepa' => 5, // "EFT"
'pi_paypal' => 3, // "Debit"
'groups' => '',
));
}
/**
* Retrieves the profile that matches the given project ID, i.e. the profile
* which is responsible for processing the project's data.
* Returns the default profile if no match was found.
*
* @param $project_id
*
* @return CRM_Twingle_Profile
*/
public static function getProfileForProject($project_id) {
$profiles = self::getProfiles();
foreach ($profiles as $profile) {
if ($profile->matches($project_id)) {
return $profile;
}
}
// No profile matched, return default profile.
return $profiles['default'];
}
/**
* Retrieves the profil with the given name.
*
* @param $name
*
* @return CRM_Twingle_Profile | NULL
*/
public static function getProfile($name) {
$profiles = self::getProfiles();
if (isset($profiles[$name])) {
return $profiles[$name];
}
else {
return NULL;
}
}
/**
* Retrieves the list of all profiles persisted within the current CiviCRM
* settings, including the default profile.
*
* @return CRM_Twingle_Profile[]
*/
public static function getProfiles() {
if (self::$_profiles === NULL) {
self::$_profiles = array();
if ($profiles_data = CRM_Core_BAO_Setting::getItem('de.systopia.twingle', 'twingle_profiles')) {
foreach ($profiles_data as $profile_name => $profile_data) {
self::$_profiles[$profile_name] = new CRM_Twingle_Profile($profile_name, $profile_data);
}
}
}
// Include the default profile if it was not overridden within the settings.
if (!isset(self::$_profiles['default'])) {
self::$_profiles['default'] = self::createDefaultProfile();
self::storeProfiles();
}
return self::$_profiles;
}
/**
* Persists the list of profiles into the CiviCRM settings.
*/
public static function storeProfiles() {
$profile_data = array();
foreach (self::$_profiles as $profile_name => $profile) {
$profile_data[$profile_name] = $profile->data;
}
CRM_Core_BAO_Setting::setItem((object) $profile_data, 'de.systopia.twingle', 'twingle_profiles');
}
}

237
CRM/Twingle/Submission.php Normal file
View file

@ -0,0 +1,237 @@
<?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;
class CRM_Twingle_Submission {
/**
* The default ID of the "Work" location type.
*/
const LOCATION_TYPE_ID_WORK = 2;
/**
* The default ID of the "Employer of" relationship type.
*/
const EMPLOYER_RELATIONSHIP_TYPE_ID = 5;
/**
* @param array &$params
* A reference to the parameters array of the submission.
*
* @param \CRM_Twingle_Profile $profile
* The Twingle profile to use for validation, defaults to the default
* profile.
*
* @throws \CiviCRM_API3_Exception
* When invalid parameters have been submitted.
*/
public static function validateSubmission(&$params, $profile = NULL) {
if (!$profile) {
$profile = CRM_Twingle_Profile::createDefaultProfile();
}
// Validate donation rhythm.
if (!in_array($params['donation_rhythm'], array(
'one_time',
'halfyearly',
'quarterly',
'yearly',
'monthly',
))) {
throw new CiviCRM_API3_Exception(
E::ts('Invalid donation rhythm.'),
'invalid_format'
);
}
// Get the payment instrument defined within the profile, or return an error
// if none matches (i.e. an unknown payment method was submitted).
if (!$payment_instrument_id = $profile->getAttribute('pi_' . $params['payment_method'])) {
throw new CiviCRM_API3_Exception(
E::ts('Payment method could not be matched to existing payment instrument.'),
'invalid_format'
);
}
$params['payment_instrument_id'] = $payment_instrument_id;
// Validate date for parameter "confirmed_at".
if (!DateTime::createFromFormat('Ymd', $params['confirmed_at'])) {
throw new CiviCRM_API3_Exception(
E::ts('Invalid date for parameter "confirmed_at".'),
'invalid_format'
);
}
// Validate date for parameter "user_birthdate".
if (!empty($params['user_birthdate']) && !DateTime::createFromFormat('Ymd', $params['user_birthdate'])) {
throw new CiviCRM_API3_Exception(
E::ts('Invalid date for parameter "user_birthdate".'),
'invalid_format'
);
}
// Get the gender ID defined within the profile, or return an error if none
// matches (i.e. an unknown gender was submitted).
if (!$gender_id = $profile->getAttribute('gender_' . $params['user_gender'])) {
throw new CiviCRM_API3_Exception(
E::ts('Gender could not be matched to existing gender.'),
'invalid_format'
);
}
$params['gender_id'] = $gender_id;
}
/**
* Retrieves the contact matching the given contact data or creates a new
* contact.
*
* @param string $contact_type
* The contact type to look for/to create.
* @param array $contact_data
* Data to use for contact lookup/to create a contact with.
*
* @return int | NULL
* The ID of the matching/created contact, or NULL if no matching contact
* was found and no new contact could be created.
* @throws \CiviCRM_API3_Exception
* When invalid data was given.
*/
public static function getContact($contact_type, $contact_data) {
// If no parameters are given, do nothing.
if (empty($contact_data)) {
return NULL;
}
// Prepare values: country.
if (!empty($contact_data['country'])) {
if (is_numeric($contact_data['country'])) {
// If a country ID is given, update the parameters.
$contact_data['country_id'] = $contact_data['country'];
unset($contact_data['country']);
}
else {
// Look up the country depending on the given ISO code.
$country = civicrm_api3('Country', 'get', array('iso_code' => $contact_data['country']));
if (!empty($country['id'])) {
$contact_data['country_id'] = $country['id'];
unset($contact_data['country']);
}
else {
throw new \CiviCRM_API3_Exception(
E::ts('Unknown country %1.', array(1 => $contact_data['country'])),
'invalid_format'
);
}
}
}
// Pass to XCM.
$contact_data['contact_type'] = $contact_type;
$contact = civicrm_api3('Contact', 'getorcreate', $contact_data);
if (empty($contact['id'])) {
return NULL;
}
return $contact['id'];
}
/**
* Shares an organisation's work address, unless the contact already has one.
*
* @param $contact_id
* The ID of the contact to share the organisation address with.
* @param $organisation_id
* The ID of the organisation whose address to share with the contact.
* @param $location_type_id
* The ID of the location type to use for address lookup.
*
* @return boolean
* Whether the organisation address has been shared with the contact.
*
* @throws \CiviCRM_API3_Exception
* When looking up or creating the shared address failed.
*/
public static function shareWorkAddress($contact_id, $organisation_id, $location_type_id = self::LOCATION_TYPE_ID_WORK) {
if (empty($organisation_id)) {
// Only if organisation exists.
return FALSE;
}
// Check whether organisation has a WORK address.
$existing_org_addresses = civicrm_api3('Address', 'get', array(
'contact_id' => $organisation_id,
'location_type_id' => $location_type_id));
if ($existing_org_addresses['count'] <= 0) {
// Organisation does not have a WORK address.
return FALSE;
}
// Check whether contact already has a WORK address.
$existing_contact_addresses = civicrm_api3('Address', 'get', array(
'contact_id' => $contact_id,
'location_type_id' => $location_type_id));
if ($existing_contact_addresses['count'] > 0) {
// Contact already has a WORK address.
return FALSE;
}
// Create a shared address.
$address = reset($existing_org_addresses['values']);
$address['contact_id'] = $contact_id;
$address['master_id'] = $address['id'];
unset($address['id']);
civicrm_api3('Address', 'create', $address);
return TRUE;
}
/**
* Updates or creates an employer relationship between contact and
* organisation.
*
* @param int $contact_id
* The ID of the employee contact.
* @param int $organisation_id
* The ID of the employer contact.
*
* @throws \CiviCRM_API3_Exception
*/
public static function updateEmployerRelation($contact_id, $organisation_id) {
if (empty($contact_id) || empty($organisation_id)) {
return;
}
// see if there is already one
$existing_relationship = civicrm_api3('Relationship', 'get', array(
'relationship_type_id' => self::EMPLOYER_RELATIONSHIP_TYPE_ID,
'contact_id_a' => $contact_id,
'contact_id_b' => $organisation_id,
'is_active' => 1,
));
if ($existing_relationship['count'] == 0) {
// There is currently no (active) relationship between these contacts.
$new_relationship_data = array(
'relationship_type_id' => self::EMPLOYER_RELATIONSHIP_TYPE_ID,
'contact_id_a' => $contact_id,
'contact_id_b' => $organisation_id,
'is_active' => 1,
);
civicrm_api3('Relationship', 'create', $new_relationship_data);
}
}
}