Skip to content

Commit

Permalink
[#20] implementing contact custom field handler
Browse files Browse the repository at this point in the history
  • Loading branch information
bjendres committed Oct 9, 2019
1 parent 4f3a077 commit b48620c
Show file tree
Hide file tree
Showing 3 changed files with 485 additions and 4 deletions.
375 changes: 375 additions & 0 deletions CRM/I3val/Handler/ContactCustomUpdate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,375 @@
<?php
/*-------------------------------------------------------+
| Ilja's Input Validation Extension |
| Amnesty International Vlaanderen |
| Copyright (C) 2019 SYSTOPIA |
| Author: B. Endres (endres@systopia.de) |
| http://www.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_I3val_ExtensionUtil as E;

/**
* this class will handle performing the changes
* that are passed on from the API call
*/
class CRM_I3val_Handler_ContactCustomUpdate extends CRM_I3val_ActivityHandler {

public static $group_name = 'i3val_contact_custom_updates';
public static $field2label = NULL;

public function getField2Label() {
if (self::$field2label === NULL) {
self::$field2label = ['value' => E::ts('Value')];
}
return self::$field2label;
}

/**
* get the main key/identifier for this handler
*/
public function getKey() {
return 'contact';
}

/**
* get a human readable name for this handler
*/
public function getName() {
return E::ts("Contact Custom Fields Update");
}

/**
* returns a list of CiviCRM entities this handler can process
*/
public function handlesEntities() {
return array('Contact');
}

/**
* get the list of
*/
public function getFields() {
$field2label = $this->getField2Label();
return array_keys($field2label);
}

/**
* Get the JSON specification file defining the custom group used for this data
*/
public function getCustomGroupSpeficationFiles() {
return array(__DIR__ . '/../../../resources/contact_custom_updates_custom_group.json');
}

/**
* Get the custom group name
*/
public function getCustomGroupName() {
return self::$group_name;
}

/**
* Verify whether the changes make sense
*
* @return array $key -> error message
*/
public function verifyChanges($activity, $values, $objects = array()) {
// TODO: check?
return array();
}

/**
* Apply the changes
*
* @return array with changes to the activity
*/
public function applyChanges($activity, $values, $objects = array()) {
$contact = $objects['contact'];
$activity_update = array();
if (!$this->hasData($activity)) {
// NO DATA, no updates
return $activity_update;
}

// calculate generic update
$contact_update = array();
$this->applyUpdateData($contact_update, $values, '%s', "%s_applied");

// remove the ones that are not flagged as 'apply'
foreach (array_keys($contact_update) as $key) {
$apply_key = "{$key}_apply";
if (!isset($values[$apply_key]) || !strlen($values[$apply_key])) {
unset($contact_update[$key]);
}
}
$this->applyUpdateData($activity_update, $contact_update, self::$group_name . '.%s_applied', '%s');

// execute update
if (!empty($contact_update)) {
$contact_update['id'] = $contact['id'];
$this->resolveFields($contact_update);
$this->resolvePreferredLanguageToLabel($contact_update, FALSE);
CRM_I3val_Session::log("UPDATE contact " . json_encode($contact_update));
civicrm_api3('Contact', 'create', $contact_update);
}

return $activity_update;
}


/**
* Load and assign necessary data to the form
*/
public function renderActivityData($activity, $form) {
$config = CRM_I3val_Configuration::getConfiguration();
$field2label = self::getField2Label();
$values = $this->compileValues(self::$group_name, $field2label, $activity);
$this->resolvePreferredLanguageToLabel($form->contact);
$this->addCurrentValues($values, $form->contact);

// exceptions for current values
if (isset($values['prefix']) && !empty($form->contact['individual_prefix'])) {
$values['prefix']['current'] = $form->contact['individual_prefix'];
}
if (isset($values['suffix']) && !empty($form->contact['individual_suffix'])) {
$values['suffix']['current'] = $form->contact['individual_suffix'];
}

// create input fields and apply checkboxes
$active_fields = array();
$checkbox_fields = array(); // these will be displayed as checkboxes rather than strings

foreach ($field2label as $fieldname => $fieldlabel) {
// if there is no values, omit field
if ($config->clearingFieldsAllowed()) {
if (empty($values[$fieldname]['submitted']) && empty($values[$fieldname]['original'])) {
continue;
}
} else {
if (!isset($values[$fieldname]['submitted']) || !strlen($values[$fieldname]['submitted'])) {
continue;
}
}

// this field has data:
$active_fields[$fieldname] = $fieldlabel;

// generate input field
if (in_array($fieldname, array('prefix', 'suffix', 'gender'))) {
// add the text input
$form->add(
'select',
"{$fieldname}_applied",
$fieldlabel,
$this->getOptionList($fieldname)
);

} elseif ($fieldname == 'birth_date' || $fieldname == 'deceased_date') {
$form->addDate(
"{$fieldname}_applied",
$fieldlabel,
FALSE,
array('formatType' => 'activityDate')
);

// format date (drop time)
if (isset($values[$fieldname]['submitted'])) {
$values[$fieldname]['submitted'] = substr($values[$fieldname]['submitted'], 0, 10);
}
if (isset($values[$fieldname]['original'])) {
$values[$fieldname]['original'] = substr($values[$fieldname]['original'], 0, 10);
}

} elseif ($fieldname == 'preferred_language') {
$form->add(
'select',
"{$fieldname}_applied",
$fieldlabel,
$this->getOptionValueList('languages', 'label', E::ts("none"))
);



} elseif (substr($fieldname, 0, 3) == 'do_' || substr($fieldname, 0, 3) == 'is_') {
$checkbox_fields[$fieldname] = 1;
$form->add(
'checkbox',
"{$fieldname}_applied",
$fieldlabel
);

} else {
// add the text input
$form->add(
'text',
"{$fieldname}_applied",
$fieldlabel
);
}

if (!empty($values[$fieldname]['applied'])) {
$form->setDefaults(array("{$fieldname}_applied" => $values[$fieldname]['applied']));
} else {
$form->setDefaults(array("{$fieldname}_applied" => isset($values[$fieldname]['submitted']) ? $values[$fieldname]['submitted'] : ''));
}

// add the apply checkbox
$form->add(
'checkbox',
"{$fieldname}_apply",
$fieldlabel
);
$form->setDefaults(array("{$fieldname}_apply" => 1));
}

$form->assign('i3val_contact_fields', $field2label);
$form->assign('i3val_contact_values', $values);
$form->assign('i3val_active_contact_fields', $active_fields);
$form->assign('i3val_active_contact_checkboxes', $checkbox_fields);
}

/**
* Get the path of the template rendering the form
*/
public function getTemplate() {
return 'CRM/I3val/Handler/ContactUpdate.tpl';
}

/**
* Calculate the data to be created and add it to the $activity_data Activity.create params
* @todo specify
*/
public function generateDiffData($entity, $submitted_data, &$activity_data) {
if ($entity != 'Contact') {
throw new Exception("Can only process Contact entity");
}

// make sure the custom fields are in the 'custom_xx' format
CRM_I3val_CustomData::resolveCustomFields($submitted_data);

// get all custom fields
$submitted_custom_data = [];
$custom_field_ids = [];
foreach ($submitted_data as $field_name => $field_data) {
if (preg_match('/^custom_(?<custom_field_id>[0-9]+)$/', $field_name, $match)) {
$submitted_custom_data[$field_name] = $field_data;
$custom_field_ids[] = $match['custom_field_id'];
}
}

if (!empty($submitted_custom_data)) {
// load that custom data
$current_custom_data = [];
$custom_data_query = civicrm_api3('CustomValue', 'get', [
'option.limit' => 0,
'entity_id' => $submitted_data['id'],
'entity_table' => 'civicrm_contact']);
foreach ($custom_data_query['values'] as $field_id => $field_data) {
$current_custom_data["custom_{$field_id}"] = $field_data['latest'];
}

// resolve / diff
$change_index = 1;
foreach ($custom_field_ids as $field_id) {
$submitted_value = CRM_Utils_Array::value("custom_{$field_id}", $submitted_custom_data, '');
$current_value = CRM_Utils_Array::value("custom_{$field_id}", $current_custom_data, '');
if ($this->differs($field_id, $submitted_value, $current_value)) {
// there is a difference -> add a record
$custom_id_field = CRM_I3val_CustomData::getCustomFieldKey(self::$group_name, 'custom_field_id');
$original_field = CRM_I3val_CustomData::getCustomFieldKey(self::$group_name, 'value_original');
$submitted_field = CRM_I3val_CustomData::getCustomFieldKey(self::$group_name, 'value_submitted');
$activity_data["{$custom_id_field}:-{$change_index}"] = $field_id;
$activity_data["{$original_field}:-{$change_index}"] = $current_value;
$activity_data["{$submitted_field}:-{$change_index}"] = $submitted_value;
$change_index++;
}
}
}
}

/**
* Check whether the presented values differ in the context of the given custom field
*
* @param $field_id integer custom field id
* @param $value1 mixed value 1
* @param $value2 mixed value 2
* @return boolean
*/
protected function differs($field_id, $value1, $value2) {
// todo: implement
return $value1 != $value2;
}

/**
* Resolve the text field names (e.g. 'gender')
* to their ID representations ('gender_id').
*/
protected function resolveFields(&$data, $add_default = FALSE) {
parent::resolveFields($data, $add_default);
$this->resolveOptionValueField($data, 'gender', 'gender', 'gender_id');
$this->resolveOptionValueField($data, 'individual_prefix', 'prefix', 'prefix_id');
$this->resolveOptionValueField($data, 'individual_suffix', 'suffix', 'suffix_id');
}


/**
* Get dropdown lists
*/
protected function getOptionList($fieldname) {
$option_group_name = NULL;

switch ($fieldname) {
case 'gender':
return $this->getOptionValueList('gender', 'label', E::ts('none'));

case 'prefix':
return $this->getOptionValueList('individual_prefix', 'label', E::ts('none'));

case 'suffix':
return $this->getOptionValueList('individual_suffix', 'label', E::ts('none'));

default:
return $this->getOptionValueList($fieldname);
}
}

/**
* Since the brilliant preferred_language field has no *_id
* counterpart, we are forced to decide whether we want to
* store the key or the label. The API needs the ID, while
* our activities will store the label
*/
protected function resolvePreferredLanguageToLabel(&$data, $to_label = TRUE) {
if (!empty($data['preferred_language'])) {
if ($to_label) {
// we want to make sure that the label is used...
if (preg_match("#^[a-z]{2}_[A-Z]{2}$#", $data['preferred_language'])) {
// ... but it is the key -> find the right label
$languages = $this->getOptionValues('languages', 'name');
if (isset($languages[$data['preferred_language']])) {
$data['preferred_language'] = $languages[$data['preferred_language']]['label'];
} else {
// not found
unset($data['preferred_language']);
}
}

} else {
// we want to make sure that the key is there...
if (!preg_match("#^[a-z]{2}_[A-Z]{2}$#", $data['preferred_language'])) {
// ...but it seems like it isn't -> find best match
$option_value = $this->getMatchingOptionValue('languages', $data['preferred_language'], TRUE, 'name');
$data['preferred_language'] = $option_value['name'];
}
}
}
}
}
Loading

0 comments on commit b48620c

Please sign in to comment.