webform_civicrm-8.x-5.0-beta3/src/Utils.php
src/Utils.php
<?php
namespace Drupal\webform_civicrm;
/**
* @file
* Webform CiviCRM module's common utility functions.
*/
use Drupal\Component\Utility\Html;
use Drupal\Core\Render\Markup;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\webform\WebformInterface;
class Utils implements UtilsInterface {
/**
* The related request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
private $requestStack;
/**
* Constructs a utils object.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
*/
function __construct(RequestStack $requestStack) {
$this->requestStack = $requestStack;
}
/**
* Explodes form key into an array and verifies that it is in the right format
*
* @param $key
* Webform component field key (string)
*
* @return array or NULL
*/
public function wf_crm_explode_key($key) {
$pieces = explode('_', $key, 6);
if (count($pieces) !== 6 || $pieces[0] !== 'civicrm') {
return NULL;
}
return $pieces;
}
/**
* Get options for a specific field
*
* @param array $field
* Webform component array
* @param string $context
* Where is this being called from?
* @param array $data
* Array of crm entity data
*
* @return array
*/
public static function wf_crm_field_options($field, $context, $data) {
return \Drupal::service('webform_civicrm.field_options')->get($field, $context, $data);
}
/**
* Fetches CiviCRM field data.
*
* @param string $var
* Name of variable to return: fields, tokens, or sets
*
* @return array
* @return array
* fields: The CiviCRM contact fields this module supports
* tokens: Available tokens keyed to field ids
* sets: Info on fieldsets (entities)
*/
public function wf_crm_get_fields($var = 'fields') {
return \Drupal::service('webform_civicrm.fields')->get($var);
}
/**
* Get list of states, keyed by ID.
* @param null|int|string $param
*/
public function wf_crm_get_states($param = NULL) {
$ret = [];
if (!$param || $param == 'default') {
$settings = $this->wf_civicrm_api('Setting', 'get', [
'sequential' => 1,
'return' => 'provinceLimit',
]);
$provinceLimit = wf_crm_aval($settings, "values:0:provinceLimit");
if (!$param && $provinceLimit) {
$param = (array) $provinceLimit;
}
else {
$settings = $this->wf_civicrm_api('Setting', 'get', [
'sequential' => 1,
'return' => 'defaultContactCountry',
]);
$param = [(int) wf_crm_aval($settings, "values:0:defaultContactCountry", 1228)];
}
}
else {
$param = [(int) $param];
}
$states = $this->wf_crm_apivalues('state_province', 'get', [
'return' => 'abbreviation,name',
'sort' => 'name',
'country_id' => ['IN' => $param],
]);
foreach ($states as $state) {
$ret[$state['id']] = $state['name'];
}
// Localize the state/province names if in an non-en_US locale
$tsLocale = \CRM_Utils_System::getUFLocale();
if ($tsLocale !== '' && $tsLocale !== 'en_US') {
$i18n = \CRM_Core_I18n::singleton();
$i18n->localizeArray($ret, ['context' => 'province']);
\CRM_Utils_Array::asort($ret);
}
return $ret;
}
/**
* Get list of events.
*
* @param array $reg_options
* @param string $context
*
* @return array
*/
function wf_crm_get_events($reg_options, $context) {
static $ret = [];
if ($ret && $context !== 'config_form') {
return $ret;
}
$format = wf_crm_aval($reg_options, 'title_display', 'title');
$sort_field = wf_crm_aval($reg_options, 'event_sort_field', 'start_date');
$sort_order = ($context == 'config_form' && $sort_field === 'start_date') ? ' DESC' : '';
$params = [
'return' => ['id', 'title', 'start_date', 'end_date', 'event_type_id', 'max_participants'],
'is_template' => 0,
'is_active' => 1,
];
$event_types = array_filter((array) $reg_options['event_type'], "is_numeric");
if ($event_types) {
$params['event_type_id'] = ['IN' => $event_types];
}
if (is_numeric(wf_crm_aval($reg_options, 'show_public_events'))) {
$params['is_public'] = $reg_options['show_public_events'];
}
$params['options'] = ['sort' => $sort_field . $sort_order];
$values = $this->wf_crm_apivalues('Event', 'get', $params);
// 'now' means only current events, 1 means show all past events, other values are relative date strings
$date_past = wf_crm_aval($reg_options, 'show_past_events', 'now');
if ($date_past != '1') {
$date_past = date('Y-m-d H:i:s', strtotime($date_past));
foreach ($values as $key => $value) {
if (isset($value['end_date']) && $value['end_date'] <= $date_past) {
unset($values[$key]);
}
}
}
// 'now' means only past events, 1 means show all future events, other values are relative date strings
$date_future = wf_crm_aval($reg_options, 'show_future_events', '1');
if ($date_future != '1') {
$date_future = date('Y-m-d H:i:s', strtotime($date_future));
foreach ($values as $key => $value) {
if (isset($value['end_date']) && $value['end_date'] >= $date_future) {
unset($values[$key]);
}
}
}
// A "full" event is one where the maximum participants is less than or equal to the number of registered participants (whose roles and statuses count toward the registration cap).
// FIXME: When we move to API4, we should ensure Event.get has a calculated "registered_participants" field tp avoid an API call per event.
// For now, keep the "show full" check last to minimize the API calls.
if (!wf_crm_aval($reg_options, 'show_full_events', '1', TRUE)) {
$rolesThatCount = array_column($this->wf_crm_apivalues('OptionValue', 'get', ['option_group_id' => "participant_role", 'filter' => 1]), 'value');
$statusesThatCount = array_column((array) $this->wf_civicrm_api4('ParticipantStatusType', 'get', [
'select' => ['id'],
'where' => [['is_counted', '=', TRUE]],
'checkPermissions' => FALSE,
]), 'id');
foreach ($values as $key => $value) {
if (!empty($value['max_participants'])) {
$registrationCount = $this->wf_civicrm_api('Participant', 'getcount', [
'event_id' => $key,
'role_id' => ['IN' => $rolesThatCount],
'status_id' => ['IN' => $statusesThatCount],
]);
if ($registrationCount >= $value['max_participants']) {
unset($values[$key]);
}
}
}
}
foreach ($values as $value) {
$ret[$value['id'] . '-' . $value['event_type_id']] = $this->wf_crm_format_event($value, $format);
}
return $ret;
}
/**
* @param array $event
* @param string $format
* @return string
*/
function wf_crm_format_event($event, $format) {
$format = explode(' ', $format);
// Date format
foreach ($format as $value) {
if (strpos($value, 'dateformat') === 0) {
$date_format = $this->wf_crm_get_civi_setting($value);
}
}
$title = [];
if (in_array('title', $format)) {
$title[] = $event['title'];
}
if (in_array('type', $format)) {
$types = $this->wf_crm_apivalues('event', 'getoptions', [
'field' => 'event_type_id',
'context' => 'get',
]);
$title[] = $types[$event['event_type_id']];
}
if (in_array('start', $format) && !empty($event['start_date'])) {
$title[] = \CRM_Utils_Date::customFormat($event['start_date'], $date_format);
}
if (in_array('end', $format) && isset($event['end_date'])) {
// Avoid showing redundant end-date if it is the same as the start date
$same_day = substr($event['start_date'], 0, 10) == substr($event['end_date'], 0, 10);
if (!$same_day || in_array('dateformatDatetime', $format) || in_array('dateformatTime', $format)) {
$end_format = (in_array('dateformatDatetime', $format) && $same_day) ? $this->wf_crm_get_civi_setting('dateformatTime') : $date_format;
$title[] = \CRM_Utils_Date::customFormat($event['end_date'], $end_format);
}
}
return implode(' - ', $title);
}
/**
* Fetch tags within a given tagset
*
* If no tagset specified, all tags NOT within a tagset are returned.
* Return format is a flat array with some tic marks to indicate nesting.
*
* @param string $used_for
* @param int $parent_id
* @return array
*/
function wf_crm_get_tags($used_for, $parent_id = NULL) {
$params = [
'used_for' => ['LIKE' => "%civicrm_{$used_for}%"],
'is_tagset' => 0,
'is_selectable' => 1,
'parent_id' => $parent_id ?: ['IS NULL' => 1],
'options' => ['sort' => 'name'],
];
$tag_display_field = $this->tag_display_field();
$tags = $this->wf_crm_apivalues('Tag', 'get', $params, $tag_display_field);
// Tagsets cannot be nested so no need to fetch children
if ($parent_id || !$tags) {
return $tags;
}
// Fetch child tags
unset($params['parent_id']);
$params += ['return' => [$tag_display_field, 'parent_id'], 'parent_id.is_tagset' => 0, 'parent_id.is_selectable' => 1, 'parent_id.used_for' => $params['used_for']];
$unsorted = $this->wf_crm_apivalues('Tag', 'get', $params);
$parents = array_fill_keys(array_keys($tags), ['depth' => 1]);
// Place children under their parents.
$prevLoop = NULL;
while ($unsorted && count($unsorted) !== $prevLoop) {
// If count stops going down then we are left with only orphan tags & will abort the loop
$prevLoop = count($unsorted);
foreach ($unsorted as $id => $tag) {
$parent = $tag['parent_id'];
if (isset($parents[$parent])) {
$name = str_repeat('- ', $parents[$parent]['depth']) . $tag[$tag_display_field];
$pos = array_search($parents[$parent]['child'] ?? $parent, array_keys($tags)) + 1;
$tags = array_slice($tags, 0, $pos, TRUE) + [$id => $name] + array_slice($tags, $pos, NULL, TRUE);
$parents[$id] = ['depth' => $parents[$parent]['depth'] + 1];
$parents[$parent]['child'] = $id;
unset($unsorted[$id]);
}
}
}
return $tags;
}
/**
* Get list of surveys
* @param array $act
*
* @return array
*/
function wf_crm_get_surveys($act = []) {
return $this->wf_crm_apivalues('survey', 'get', array_filter($act), 'title');
}
/**
* Get activity types related to CiviCampaign
* @return array
*/
function wf_crm_get_campaign_activity_types() {
$ret = [];
if (array_key_exists('activity_survey_id', $this->wf_crm_get_fields())) {
$vals = $this->wf_crm_apivalues('option_value', 'get', [
'option_group_id' => 'activity_type',
'is_active' => 1,
'component_id' => 'CiviCampaign',
]);
foreach ($vals as $val) {
$ret[$val['value']] = $val['label'];
}
}
return $ret;
}
/**
* Get contact types and sub-types
* Unlike pretty much every other option list CiviCRM wants "name" instead of "id"
*
* @return array
*/
function wf_crm_get_contact_types() {
static $contact_types = [];
static $sub_types = [];
if (!$contact_types) {
$data = $this->wf_crm_apivalues('contact_type', 'get', ['is_active' => 1]);
foreach ($data as $type) {
if (empty($type['parent_id'])) {
$contact_types[strtolower($type['name'])] = $type['label'];
continue;
}
$sub_types[strtolower($data[$type['parent_id']]['name'])][$type['name']] = $type['label'];
}
}
return [$contact_types, $sub_types];
}
/**
* In reality there is no contact field 'privacy' so this is not a real option list.
* These are actually 5 separate contact fields that this module munges into 1 for better usability.
*
* @return array
*/
function wf_crm_get_privacy_options() {
return [
'do_not_email' => ts('Do not email'),
'do_not_phone' => ts('Do not phone'),
'do_not_mail' => ts('Do not mail'),
'do_not_sms' => ts('Do not sms'),
'do_not_trade' => ts('Do not trade'),
'is_opt_out' => ts('NO BULK EMAILS (User Opt Out)'),
];
}
/**
* Get relationship type data
*
* @return array
*/
function wf_crm_get_relationship_types() {
static $types = [];
if (!$types) {
foreach ($this->wf_crm_apivalues('relationship_type', 'get', ['is_active' => 1]) as $r) {
$r['type_a'] = strtolower(wf_crm_aval($r, 'contact_type_a') ?? '');
$r['type_b'] = strtolower(wf_crm_aval($r, 'contact_type_b') ?? '');
$r['sub_type_a'] = wf_crm_aval($r, 'contact_sub_type_a');
if (!is_null($r['sub_type_a'])) {
$r['sub_type_a'] = $r['sub_type_a'];
}
$r['sub_type_b'] = wf_crm_aval($r, 'contact_sub_type_b');
if (!is_null($r['sub_type_b'])) {
$r['sub_type_b'] = $r['sub_type_b'];
}
$types[$r['id']] = $r;
}
}
return $types;
}
/**
* Get valid relationship types for a given pair of contacts
*
* @param $type_a
* Contact type
* @param $type_b
* Contact type
* @param $sub_type_a
* Contact sub-type
* @param $sub_type_b
* Contact sub-type
*
* @return array
*/
function wf_crm_get_contact_relationship_types($type_a, $type_b, $sub_type_a, $sub_type_b) {
$ret = [];
foreach ($this->wf_crm_get_relationship_types() as $t) {
$reciprocal = ($t['label_a_b'] != $t['label_b_a'] && $t['label_b_a'] || $t['type_a'] != $t['type_b'] || $t['sub_type_a'] != $t['sub_type_b']);
if (($t['type_a'] == $type_a || !$t['type_a'])
&& ($t['type_b'] == $type_b || !$t['type_b'])
&& (in_array($t['sub_type_a'], $sub_type_a) || !$t['sub_type_a'])
&& (in_array($t['sub_type_b'], $sub_type_b) || !$t['sub_type_b'])
) {
$ret[$t['id'] . ($reciprocal ? '_a' : '_r')] = $t['label_a_b'];
}
// Reciprocal form - only show if different from above
if ($reciprocal
&& ($t['type_a'] == $type_b || !$t['type_a'])
&& ($t['type_b'] == $type_a || !$t['type_b'])
&& (in_array($t['sub_type_a'], $sub_type_b) || !$t['sub_type_a'])
&& (in_array($t['sub_type_b'], $sub_type_a) || !$t['sub_type_b'])
) {
$ret[$t['id'] . '_b'] = $t['label_b_a'];
}
}
return $ret;
}
/**
* List dedupe rules available for a contact type
*
* @param string $contact_type
* @return array
*/
function wf_crm_get_matching_rules($contact_type) {
static $rules;
$contact_type = ucfirst($contact_type);
if (!$rules) {
$rules = array_fill_keys(['Individual', 'Organization', 'Household'], []);
$values = $this->wf_crm_apivalues('RuleGroup', 'get');
foreach ($values as $value) {
$rules[$value['contact_type']][$value['id']] = $value['title'];
}
}
return $rules[$contact_type];
}
/**
* Get ids or values of enabled CiviCRM fields for a webform.
*
* @param \Drupal\webform\WebformInterface $webform
* The webform.
* @param array|null $submission
* (optional) if supplied, will match field keys with submitted values
* @param boolean $show_all
* (optional) if true, get every field even if it belongs to a contact that does not exist
*
* @return array of enabled fields
*/
function wf_crm_enabled_fields(WebformInterface $webform, $submission = NULL, $show_all = FALSE) {
$enabled = [];
$elements = $webform->getElementsDecodedAndFlattened();
if (!empty($elements) || ($show_all)) {
$handler_collection = $webform->getHandlers('webform_civicrm');
if (!$handler_collection->has('webform_civicrm')) {
return $enabled;
}
/** @var \Drupal\webform\Plugin\WebformHandlerInterface $handler */
$handler = $handler_collection->get('webform_civicrm');
$handler_configuration = $handler->getConfiguration();
$fields = $this->wf_crm_get_fields();
foreach ($elements as $key => $c) {
$exp = explode('_', $key, 5);
$customGroupFieldsetKey = '';
if (count($exp) == 5) {
[$lobo, $i, $ent, $n, $id] = $exp;
if ($lobo != 'civicrm') {
continue;
}
$explodedId = explode('_', $id);
if (wf_crm_aval($explodedId, 1) == 'fieldset' && $explodedId[0] != 'fieldset') {
$customGroupFieldsetKey = $explodedId[0];
// Automatically enable 'Create mode' field for Contact's custom group.
if ($ent === 'contact') {
$enabled[$lobo . '_' . $i . '_' . $ent . '_' . $n . '_' . $customGroupFieldsetKey . '_createmode'] = 1;
}
}
$fieldSetIds = ['fieldset_fieldset', "{$customGroupFieldsetKey}_fieldset", "number_of_billing_1_fieldset_fieldset"];
if ((isset($fields[$id]) || (in_array($id, $fieldSetIds))) && is_numeric($i) && is_numeric($n)) {
if (!$show_all && ($ent == 'contact' || $ent == 'participant') && empty($handler_configuration['settings']['data']['contact'][$i])) {
continue;
}
if ($submission) {
$enabled[$key] = wf_crm_aval($submission, $c['#form_key'], NULL, TRUE);
}
else {
$enabled[$key] = $key;
}
}
}
}
}
return $enabled;
}
/**
* Get a field based on its short or full name
* @param string $key
* @return array|null
*/
function wf_crm_get_field($key) {
$fields = $this->wf_crm_get_fields();
if (isset($fields[$key])) {
return $fields[$key];
}
if ($pieces = $this->wf_crm_explode_key($key)) {
[ , , , , $table, $name] = $pieces;
if (isset($fields[$table . '_' . $name])) {
return $fields[$table . '_' . $name];
}
}
}
/**
* Lookup a uf ID from contact ID or vice-versa
* With no arguments passed in, this function will return the contact_id of the current logged-in user
*
* @param $id
* (optional) uf or contact ID - defaults to current user
* @param $type
* (optional) what type of ID is supplied - defaults to user id
* @return int|null
*/
function wf_crm_user_cid($id = NULL, $type = 'uf') {
static $current_user = NULL;
if (!$id) {
if ($current_user !== NULL) {
return $current_user;
}
$id = $user_lookup = \Drupal::currentUser()->id();
}
if (!$id || !is_numeric($id)) {
return NULL;
}
// Lookup current domain for multisite support
static $domain = 0;
if (!$domain) {
$domain = $this->wf_civicrm_api('domain', 'get', ['current_domain' => 1, 'return' => 'id']);
$domain = wf_crm_aval($domain, 'id', 1);
}
$result = $this->wf_crm_apivalues('uf_match', 'get', [
$type . '_id' => $id,
'domain_id' => $domain,
'sequential' => 1,
]);
if ($result) {
if (!empty($user_lookup)) {
$current_user = $result[0]['contact_id'];
}
return $type == 'uf' ? $result[0]['contact_id'] : $result[0]['uf_id'];
}
}
/**
* Fetch contact display name
*
* @param $cid
* Contact id
*
* @return string
*/
function wf_crm_display_name($cid) {
if (!$cid || !is_numeric($cid)) {
return '';
}
\Drupal::getContainer()->get('civicrm')->initialize();
$result = $this->wf_civicrm_api('contact', 'get', ['id' => $cid, 'return.display_name' => 1, 'is_deleted' => 0]);
return Html::escape(wf_crm_aval($result, "values:$cid:display_name", ''));
}
/**
* @param integer $n
* @param array $data Form data
* @param string $html Controls how html should be treated. Options are:
* * 'escape': (default) Escape html characters
* * 'wrap': Escape html characters and wrap in a span
* * 'plain': Do not escape (use when passing into an FAPI options list which does its own escaping)
* @return string
*/
function wf_crm_contact_label($n, $data = [], $html = 'escape') {
$label = trim(wf_crm_aval($data, "contact:$n:contact:1:webform_label", ''));
if (!$label) {
$label = t('Contact :num', [':num' => $n]);
}
if ($html != 'plain') {
$label = Html::escape($label);
}
if ($html == 'wrap') {
$label = Markup::create($label);
}
return $label;
}
/**
* Convert a | separated string into an array
*
* @param string $str
* String representation of key => value select options
*
* @return array of select options
*/
function wf_crm_str2array($str) {
$ret = [];
if ($str) {
foreach (explode("\n", trim($str)) as $row) {
if ($row && $row[0] !== '<' && strpos($row, '|')) {
[$k, $v] = explode('|', $row);
$ret[trim($k)] = trim($v);
}
}
}
return $ret;
}
/**
* Convert an array into a | separated string
*
* @param array $arr
* Array of select options
*
* @return string
* String representation of key => value select options
*/
function wf_crm_array2str($arr) {
$str = '';
foreach ($arr as $k => $v) {
$str .= ($str ? "\n" : '') . $k . '|' . $v;
}
return $str;
}
/**
* @inheritDoc
*/
function wf_civicrm_api4($entity, $operation, $params, $index = NULL) {
if (!$entity) {
return [];
}
$params += [
'checkPermissions' => FALSE,
];
$result = civicrm_api4($entity, $operation, $params, $index);
return $result;
}
/**
* Wrapper for all CiviCRM API calls
* For consistency, future-proofing, and error handling
*
* @param string $entity
* API entity
* @param string $operation
* API operation
* @param array $params
* API params
*
* @return array
* Result of API call
*/
function wf_civicrm_api($entity, $operation, $params) {
if (!$entity) {
return [];
}
$params += [
'check_permissions' => FALSE,
];
if ($operation == 'transact') {
$utils = \Drupal::service('webform_civicrm.utils');
$result = $utils->wf_civicrm_api3_contribution_transact($params);
}
else {
$result = civicrm_api3($entity, $operation, $params);
}
// I guess we want silent errors for getoptions b/c we check it for failure separately
if (!empty($result['is_error']) && $operation != 'getoptions') {
$bt = debug_backtrace();
$n = $bt[0]['function'] == 'wf_civicrm_api' ? 1 : 0;
$file = explode('/', $bt[$n]['file']);
if (isset($params['credit_card_number'])) {
$params['credit_card_number'] = "xxxxxxxxxxxx".substr($params['credit_card_number'], -4);
}
\Drupal::logger('webform_civicrm')->error(
'The CiviCRM "@function" API returned the error: "@msg" when called by function "@fn" on line @line of @file with parameters: "@params"',
[
'@function' => $entity . ' ' . $operation,
'@msg' => $result['error_message'],
'@fn' => $bt[$n+1]['function'],
'@line' => $bt[$n]['line'],
'@file' => array_pop($file),
'@params' => print_r($params, TRUE),
]
);
}
return $result;
}
/**
* Process a transaction and record it against the contact.
*
* @deprecated
*
* @param array $params
* Input parameters.
*
* @return array
* contribution of created or updated record (or a civicrm error)
*/
function wf_civicrm_api3_contribution_transact($params) {
// Start with the same parameters as Contribution.transact.
$params['contribution_status_id'] = 'Pending';
if (!isset($params['invoice_id']) && !isset($params['invoiceID'])) {
// Set an invoice_id here if you have not already done so.
// Potentially Order api should do this https://lab.civicrm.org/dev/financial/issues/78
$params['invoice_id'] = md5(uniqid(rand(), TRUE));
}
if (isset($params['invoice_id']) && !isset($params['invoiceID'])) {
// This would be required prior to https://lab.civicrm.org/dev/financial/issues/77
$params['invoiceID'] = $params['invoice_id'];
}
elseif (!isset($params['invoice_id']) && isset($params['invoiceID'])) {
$params['invoice_id'] = $params['invoiceID'];
}
$order = civicrm_api3('Order', 'create', $params);
try {
$params['amount'] = $params['total_amount'];
$params['contribution_id'] = $order['id'];
$payParams = $params;
$payResult = reset(civicrm_api3('PaymentProcessor', 'pay', $payParams)['values']);
// webform_civicrm sends out receipts using Contribution.send_confirmation API if the contribution page is has is_email_receipt = TRUE.
// We allow this to be overridden here but default to FALSE.
$params['is_email_receipt'] = $params['is_email_receipt'] ?? FALSE;
// payment_status_id is deprecated - https://lab.civicrm.org/dev/financial/-/issues/141
if (!isset($payResult['payment_status'])) {
$payResult['payment_status'] = 'Pending';
// payment_status_id = 1 -> payment completed;
// payment_status_id = 2 -> payment NOT completed;
if ($payResult['payment_status_id'] == '1') {
$payResult['payment_status'] = 'Completed';
}
}
// Assuming the payment was taken, record it which will mark the Contribution
// as Completed and update related entities.
if ($payResult['payment_status'] === 'Completed') {
civicrm_api3('Payment', 'create', [
'contribution_id' => $order['id'],
'total_amount' => $payParams['amount'],
'fee_amount' => $payResult['fee_amount'] ?? 0,
'payment_instrument_id' => $order['values'][$order['id']]['payment_instrument_id'],
'payment_processor_id' => $payParams['payment_processor_id'],
'is_send_contribution_notification' => $params['is_email_receipt'],
'trxn_id' => $payResult['trxn_id'] ?? NULL,
]);
} else {
civicrm_api3('Contribution', 'create', [
'id' => $order['id'],
'total_amount' => $payParams['amount'],
'fee_amount' => $payResult['fee_amount'] ?? 0,
'payment_instrument_id' => $order['values'][$order['id']]['payment_instrument_id'],
'payment_processor_id' => $payParams['payment_processor_id'],
'trxn_id' => $payResult['trxn_id'] ?? NULL,
]);
}
} catch (\Exception $e) {
return ['error_message' => $e->getMessage()];
}
// Contribution.transact is expected to return an API3 result containing the contribution
// eg. [ 'id' => X, 'values' => [ X => [ contribution details ] ] return $contribution;
return civicrm_api3('Contribution', 'get', ['id' => $order['id']]);
}
/**
* Get the values from an api call
*
* @param string $entity
* API entity
* @param string $operation
* API operation
* @param array $params
* API params
* @param string $value
* Reduce each result to this single value
*
* @return array
* Values from API call
*/
function wf_crm_apivalues($entity, $operation, $params = [], $value = NULL) {
if (is_numeric($params)) {
$params = ['id' => $params];
}
$params += ['options' => []];
// Work around the api's default limit of 25
$params['options'] += ['limit' => 0];
$ret = wf_crm_aval($this->wf_civicrm_api($entity, $operation, $params), 'values', []);
if ($value) {
foreach ($ret as &$values) {
$values = wf_crm_aval($values, $value);
}
}
return $ret;
}
/**
* Check if a name or email field exists for this contact.
* This determines whether a new contact can be created on the webform.
*
* @param $enabled
* Array of enabled fields
* @param $c
* Contact #
* @param $contact_type
* Contact type
* @return int
*/
function wf_crm_name_field_exists($enabled, $c, $contact_type) {
foreach ($this->wf_crm_required_contact_fields($contact_type) as $f) {
$fid = 'civicrm_' . $c . '_contact_1_' . $f['table'] . '_' . $f['name'];
if (!empty($enabled[$fid])) {
return 1;
}
}
return 0;
}
/**
* At least one of these fields is required to create a contact
*
* @param string $contact_type
* @return array of fields
*/
function wf_crm_required_contact_fields($contact_type) {
if ($contact_type == 'individual') {
return [
['table' => 'email', 'name' => 'email'],
['table' => 'contact', 'name' => 'first_name'],
['table' => 'contact', 'name' => 'last_name'],
];
}
return [['table' => 'contact', 'name' => $contact_type . '_name']];
}
/**
* These are the contact location fields this module supports
*
* @return array
*/
function wf_crm_location_fields() {
return ['address', 'email', 'phone', 'website', 'im'];
}
/**
* These are the address fields this module supports
*
* @return array
*/
function wf_crm_address_fields() {
$fields = [];
foreach (array_keys($this->wf_crm_get_fields()) as $key) {
if (strpos($key, 'address') === 0) {
$fields[] = substr($key, 8);
}
}
return $fields;
}
/**
* @param string
* @return array
*/
function wf_crm_explode_multivalue_str($str) {
$sp = \CRM_Core_DAO::VALUE_SEPARATOR;
if (is_array($str)) {
return $str;
}
return explode($sp, trim((string) $str, $sp));
}
/**
* Check if value is a positive integer
* @param mixed $val
* @return bool
*/
function wf_crm_is_positive($val) {
return is_numeric($val) && $val > 0 && round($val) == $val;
}
/**
* Returns empty custom civicrm field sets
*
* @return array $sets
*/
function wf_crm_get_empty_sets() {
$sets = [];
$sql = "SELECT cg.id, cg.title, cg.help_pre, cg.extends, SUM(cf.is_active) as custom_field_sum
FROM civicrm_custom_group cg
LEFT OUTER JOIN civicrm_custom_field cf
ON (cg.id = cf.custom_group_id)
GROUP By cg.id";
$dao = \CRM_Core_DAO::executeQuery($sql);
while($dao->fetch()) {
// Because a set with all fields disabled = empty set
if (empty($dao->custom_field_sum)) {
$set = 'cg' . $dao->id;
if ($dao->extends == 'address' || $dao->extends == 'relationship' || $dao->extends == 'membership') {
$set = $dao->extends;
}
$sets[$set] = [
'label' => $dao->title,
'entity_type' => strtolower($dao->extends),
'help_text' => $dao->help_pre,
];
}
}
return $sets;
}
/**
* Pull custom fields to match with Webform element types
*
* @return array
*/
function wf_crm_custom_types_map_array() {
$custom_types = [
'Select' => ['type' => 'select'],
'Multi-Select' => ['type' => 'select', 'extra' => ['multiple' => 1]],
'Radio' => ['type' => 'select', 'extra' => ['aslist' => 0]],
'CheckBox' => ['type' => 'select', 'extra' => ['multiple' => 1]],
'Text' => ['type' => 'textfield'],
'TextArea' => ['type' => 'textarea'],
'RichTextEditor' => ['type' => 'text_format'],
'Select Date' => ['type' => 'date'],
'Link' => ['type' => 'textfield'],
'Select Country' => ['type' => 'select'],
'Multi-Select Country' => ['type' => 'select', 'extra' => ['multiple' => 1]],
'Select State/Province' => ['type' => 'select'],
'Multi-Select State/Province' => ['type' => 'select', 'extra' => ['multiple' => 1]],
'Autocomplete-Select' => ['type' => \Drupal::moduleHandler()->moduleExists('webform_autocomplete') ? 'autocomplete' : 'select'],
'File' => ['type' => 'file'],
];
return $custom_types;
}
/**
* @param string $setting_name
* @param mixed $default_value
* @return mixed
*/
function wf_crm_get_civi_setting($setting_name, $default_value = NULL) {
$aliases = [
'defaultCurrencySymbol' => 'defaultCurrency',
];
$settings = $this->wf_civicrm_api('Setting', 'get', [
'sequential' => 1,
'return' => str_replace(array_keys($aliases), array_values($aliases), $setting_name),
]);
// Not a real setting, requires cross-lookup
if ($setting_name == 'defaultCurrencySymbol') {
$currencies = $this->wf_crm_apivalues('Contribution', 'getoptions', [
'field' => "currency",
'context' => "abbreviate",
]);
return wf_crm_aval($currencies, $settings['values'][0]['defaultCurrency'], $default_value);
}
$result = wf_crm_aval($settings, "values:0:$setting_name", $default_value);
if ($result === 'default') {
return $default_value;
}
return $result;
}
/**
* Searches for all occurrence of form key in the array and
* unsets it from the webform element.
*
* @param array $elements
* @param string $form_key
*/
function remove_element(&$elements, $form_key) {
unset($elements[$form_key]);
foreach ($elements as $k => &$value) {
if (is_array($value)) {
$this->remove_element($value, $form_key);
}
elseif ($value === $form_key) {
unset($elements[$k]);
}
}
}
/**
* Build params for contribution receipt.
*
* @return array
*/
public function getReceiptParams($data, $contributionID) {
$contributionData = wf_crm_aval($data, 'contribution:1:contribution:1');
$params = ['id' => $contributionID];
$params['payment_processor_id'] = $contributionData['payment_processor_id'] ?? $data['civicrm_1_contribution_1_contribution_payment_processor_id'] ?? NULL;
unset($params['payment_processor']);
$params['financial_type_id'] = $contributionData['financial_type_id'] ?? $data['civicrm_1_contribution_1_contribution_financial_type_id_raw'] ?? NULL;
$params['currency'] = wf_crm_aval($data, "contribution:1:currency");
//Assign receipt values set on the webform config page.
$receipt = wf_crm_aval($data, "receipt", []);
$receiptValues = ['cc_receipt', 'bcc_receipt', 'receipt_text', 'pay_later_receipt', 'receipt_from_name', 'receipt_from_email'];
foreach ($receiptValues as $val) {
$params[$val] = $receipt["number_number_of_receipt_{$val}"] ?? '';
}
return $params;
}
/**
* Does an element support multiple values
*
* @param array $element
*/
public function hasMultipleValues($element) {
if (!empty($element['#extra']['multiple']) ||
(empty($element['#civicrm_live_options'])
&& empty($element['#extra']['aslist'])
&& !empty($element['#options']) && is_array($element['#options'])
&& count($element['#options']) === 1)) {
return TRUE;
}
return FALSE;
}
/**
* @inheritDoc
*/
public function checksumUserAccess($c, $cid) {
$request = $this->requestStack->getCurrentRequest();
$urlCidN = $urlChecksumN = NULL;
$session = \CRM_Core_Session::singleton();
$urlCid1 = $request->query->get('cid');
$urlChecksum1 = $request->query->get('cs');
$urlCidN = $request->query->get("cid$c");
$urlChecksumN = $request->query->get("cs$c");
$cs = NULL;
if ($c == 1 && !empty($urlChecksum1)) {
$cs = $urlChecksum1;
}
elseif (!empty($urlChecksumN)) {
$cs = $urlChecksumN;
}
if ($cs && (($c == 1 && $urlCid1 == $cid) || $urlCidN == $cid)) {
$check_access = $this->wf_civicrm_api4('Contact', 'validateChecksum', [
'contactId' => $cid,
'checksum' => $cs,
])[0] ?? [];
if ($check_access['valid']) {
if ($c == 1) {
$session->set('userID', $cid);
}
return TRUE;
}
}
// If access is checked for non primary contact, check if c1 has access to view it.
elseif ($c != 1 && $this->isContactAccessible($cid)) {
return TRUE;
}
// If no checksum is passed and user is anonymous, reset prev checksum session values if any.
if (\Drupal::currentUser()->isAnonymous() && $session->get('userID') && $c == 1 && empty($urlChecksum1)) {
$session->reset();
}
return FALSE;
}
/**
* @inheritDoc
*/
public function isContactAccessible($cid) {
$access = $this->wf_civicrm_api4('Contact', 'checkAccess', [
'action' => 'get',
'values' => [
'id' => $cid,
],
], 0);
if (!empty($access['access'])) {
return TRUE;
}
$request = $this->requestStack->getCurrentRequest();
$urlCid1 = $request->query->get('cid') ?? $request->query->get('cid1') ?? NULL;
$urlChecksum1 = $request->query->get('cs') ?? $request->query->get('cs1') ?? NULL;
if (!empty($urlChecksum1) && !empty($urlCid1)) {
$valid = $this->wf_civicrm_api4('Contact', 'validateChecksum', [
'contactId' => $urlCid1,
'checksum' => $urlChecksum1,
])[0] ?? [];
if ($valid['valid']) {
// checkAccess v4 api does not check for access via relationship.
if (\CRM_Contact_BAO_Contact_Permission::allow($cid)) {
return TRUE;
}
}
}
return FALSE;
}
/**
* @return string Which field is the tag display field in this version of civi?
*/
public function tag_display_field(): string {
if (version_compare(\CRM_Core_BAO_Domain::version(), '5.68.alpha1', '>=')) {
return 'label';
}
return 'name';
}
}
