3 * +--------------------------------------------------------------------+
4 * | CiviCRM version 5 |
5 * +--------------------------------------------------------------------+
6 * | Copyright CiviCRM LLC (c) 2004-2018 |
7 * +--------------------------------------------------------------------+
8 * | This file is a part of CiviCRM. |
10 * | CiviCRM is free software; you can copy, modify, and distribute it |
11 * | under the terms of the GNU Affero General Public License |
12 * | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 * | CiviCRM is distributed in the hope that it will be useful, but |
15 * | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 * | See the GNU Affero General Public License for more details. |
19 * | You should have received a copy of the GNU Affero General Public |
20 * | License and the CiviCRM Licensing Exception along |
21 * | with this program; if not, contact CiviCRM LLC |
22 * | at info[AT]civicrm[DOT]org. If you have questions about the |
23 * | GNU Affero General Public License or the licensing of CiviCRM, |
24 * | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 * +--------------------------------------------------------------------+
29 * Class api_v3_CustomValueTest
32 class api_v3_CustomValueTest
extends CiviUnitTestCase
{
33 protected $_apiversion = 3;
35 protected $optionGroup;
37 public $DBResetRequired = FALSE;
39 public function setUp() {
43 public function _populateOptionAndCustomGroup($type = NULL) {
45 'integer' => array(1, 2, 3),
46 'number' => array(10.11, 20.22, 30.33),
47 'string' => array(substr(sha1(rand()), 0, 4) . '(', substr(sha1(rand()), 0, 3) . '|', substr(sha1(rand()), 0, 2) . ','),
48 // 'country' => array_rand(CRM_Core_PseudoConstant::country(FALSE, FALSE), 3),
49 // This does not work in the test at the moment due to caching issues.
50 //'state_province' => array_rand(CRM_Core_PseudoConstant::stateProvince(FALSE, FALSE), 3),
56 $dataValues = !empty($type) ?
array($type => $dataValues[$type]) : $dataValues;
58 foreach ($dataValues as $dataType => $values) {
59 $this->optionGroup
[$dataType] = array('values' => $values);
60 if (!empty($values)) {
61 $result = $this->callAPISuccess('OptionGroup', 'create',
63 'name' => "{$dataType}_group",
64 'api.option_value.create' => array('label' => "$dataType 1", 'value' => $values[0]),
65 'api.option_value.create.1' => array('label' => "$dataType 2", 'value' => $values[1]),
66 'api.option_value.create.2' => array('label' => "$dataType 3", 'value' => $values[2]),
69 $this->optionGroup
[$dataType]['id'] = $result['id'];
71 elseif ($dataType == 'contact') {
72 for ($i = 0; $i < 3; $i++
) {
73 $result = $this->callAPISuccess('Contact', 'create', array('contact_type' => 'Individual', 'email' => substr(sha1(rand()), 0, 7) . '@yahoo.com'));
74 $this->optionGroup
[$dataType]['values'][$i] = $result['id'];
77 $this->ids
[$dataType] = $this->entityCustomGroupWithSingleFieldCreate("$dataType Custom Group", 'Contacts');
82 public function tearDown() {
83 $tablesToTruncate = array(
85 'civicrm_custom_field',
86 'civicrm_custom_group',
90 // true tells quickCleanup to drop any tables that might have been created in the test
91 $this->quickCleanup($tablesToTruncate, TRUE);
93 // cleanup created option group for each custom-set before running next test
94 if (!empty($this->optionGroup
)) {
95 foreach ($this->optionGroup
as $type => $value) {
96 if (!empty($value['id'])) {
97 $count = $this->callAPISuccess('OptionGroup', 'get', array('id' => $value['id']));
98 if ((bool) $count['count']) {
99 $this->callAPISuccess('OptionGroup', 'delete', array('id' => $value['id']));
106 public function testCreateCustomValue() {
107 $this->_populateOptionAndCustomGroup();
108 $this->_customField
= $this->customFieldCreate(array('custom_group_id' => $this->ids
['string']['custom_group_id']));
109 $this->_customFieldID
= $this->_customField
['id'];
111 $customFieldDataType = CRM_Core_BAO_CustomField
::dataType();
112 $dataToHtmlTypes = CRM_Core_BAO_CustomField
::dataToHtml();
114 $optionSupportingHTMLTypes = array('Select', 'Radio', 'CheckBox', 'Autocomplete-Select', 'Multi-Select');
116 foreach ($customFieldDataType as $dataType => $label) {
119 // case 'StateProvince':
128 //Based on the custom field data-type choose desired SQL operators(to test with) and basic $type
129 if (in_array($dataType, array('String', 'Link'))) {
130 $validSQLOperators = array('=', '!=', 'IN', 'NOT IN', 'LIKE', 'NOT LIKE', 'IS NOT NULL', 'IS NULL');
133 elseif ($dataType == 'Boolean') {
134 $validSQLOperators = array('=', '!=', 'IS NOT NULL', 'IS NULL');
138 if ($dataType == 'Country') {
141 elseif ($dataType == 'StateProvince') {
142 $type = 'state_province';
144 elseif ($dataType == 'ContactReference') {
147 elseif ($dataType == 'Date') {
151 $type = $dataType == 'Int' ?
'integer' : 'number';
153 $validSQLOperators = array('=', '!=', 'IN', 'NOT IN', '<=', '>=', '>', '<', 'IS NOT NULL', 'IS NULL');
156 //Create custom field of $dataType and html-type $html
157 foreach ($dataToHtmlTypes[$count] as $html) {
158 // per CRM-18568 the like operator does not currently work for fields with options.
159 // the LIKE operator could potentially bypass ACLs (as could IS NOT NULL) and some thought needs to be given
161 if (in_array($html, $optionSupportingHTMLTypes)) {
162 $validSQLOperators = array_diff($validSQLOperators, array('LIKE', 'NOT LIKE'));
165 'custom_group_id' => $this->ids
[$type]['custom_group_id'],
166 'label' => "$dataType - $html",
167 'data_type' => $dataType,
168 'html_type' => $html,
169 'default_value' => NULL,
171 if (!in_array($html, array('Text', 'TextArea')) && !in_array($dataType, array('Link', 'Date', 'ContactReference', 'Boolean'))) {
172 $params +
= array('option_group_id' => $this->optionGroup
[$type]['id']);
174 $customField = $this->customFieldCreate($params);
175 //Now test with $validSQLOperator SQL operators against its custom value(s)
176 $this->_testCustomValue($customField['values'][$customField['id']], $validSQLOperators, $type);
182 // skipping File data-type & state province due to caching issues
189 public function _testCustomValue($customField, $sqlOps, $type) {
190 $isSerialized = CRM_Core_BAO_CustomField
::isSerialized($customField);
191 $customId = $customField['id'];
193 'contact_type' => 'Individual',
194 'email' => substr(sha1(rand()), 0, 7) . 'man1@yahoo.com',
196 $result = $this->callAPISuccess('Contact', 'create', $params);
197 $contactId = $result['id'];
202 $selectedValue = $this->optionGroup
[$type]['values'];
203 $notselectedValue = $selectedValue[$count];
204 unset($selectedValue[$count]);
206 elseif ($customField['html_type'] == 'Link') {
207 $selectedValue = "http://" . substr(sha1(rand()), 0, 7) . ".com";
208 $notselectedValue = "http://" . substr(sha1(rand()), 0, 7) . ".com";
210 elseif ($type == 'date') {
211 $selectedValue = date('Ymd');
212 $notselectedValue = $lesserSelectedValue = date('Ymd', strtotime('yesterday'));
213 $greaterSelectedValue = date('Ymd', strtotime('+ 1 day'));
215 elseif ($type == 'contact') {
216 $selectedValue = $this->optionGroup
[$type]['values'][1];
217 $notselectedValue = $this->optionGroup
[$type]['values'][0];
219 elseif ($type == 'boolean') {
221 $notselectedValue = 0;
224 $selectedValue = $this->optionGroup
[$type]['values'][0];
225 $notselectedValue = $this->optionGroup
[$type]['values'][$count];
226 if (in_array(">", $sqlOps)) {
227 $greaterSelectedValue = $selectedValue +
1;
228 $lesserSelectedValue = $selectedValue - 1;
233 'entity_id' => $contactId,
234 'custom_' . $customId => $selectedValue,
235 "custom_{$this->_customFieldID}" => "Test String Value for {$this->_customFieldID}",
237 $this->callAPISuccess('CustomValue', 'create', $params);
239 //Test for different return value syntax.
241 ['return' => "custom_{$customId}"],
242 ['return' => ["custom_{$customId}"]],
243 ["return.custom_{$customId}" => 1],
244 ['return' => ["custom_{$customId}", "custom_{$this->_customFieldID}"]],
245 ["return.custom_{$customId}" => 1, "return.custom_{$this->_customFieldID}" => 1],
247 foreach ($returnValues as $key => $val) {
248 $params = array_merge($val, [
249 'entity_id' => $contactId,
251 $customValue = $this->callAPISuccess('CustomValue', 'get', $params);
252 if (is_array($selectedValue)) {
253 $expected = array_values($selectedValue);
254 $this->checkArrayEquals($expected, $customValue['values'][$customId]['latest']);
256 elseif ($type == 'date') {
257 $this->assertEquals($selectedValue, date('Ymd', strtotime(str_replace('.', '/', $customValue['values'][$customId]['latest']))));
260 $this->assertEquals($selectedValue, $customValue['values'][$customId]['latest']);
263 $this->assertEquals("Test String Value for {$this->_customFieldID}", $customValue['values'][$this->_customFieldID
]['latest']);
267 foreach ($sqlOps as $op) {
268 $qillOp = CRM_Utils_Array
::value($op, CRM_Core_SelectValues
::getSearchBuilderOperators(), $op);
271 $result = $this->callAPISuccess('Contact', 'Get', array('custom_' . $customId => (is_array($selectedValue) ?
implode(CRM_Core_DAO
::VALUE_SEPARATOR
, $selectedValue) : $selectedValue)));
272 $this->assertEquals($contactId, $result['id']);
276 $result = $this->callAPISuccess('Contact', 'Get', array('custom_' . $customId => array($op => $notselectedValue)));
277 $this->assertEquals(TRUE, array_key_exists($contactId, $result['values']));
287 // To be precise in for these operator we can't just rely on one contact,
288 // hence creating multiple contact with custom value less/more then $selectedValue respectively
289 $result = $this->callAPISuccess('Contact', 'create', array('contact_type' => 'Individual', 'email' => substr(sha1(rand()), 0, 7) . 'man2@yahoo.com'));
290 $contactId2 = $result['id'];
291 $this->callAPISuccess('CustomValue', 'create', array('entity_id' => $contactId2, 'custom_' . $customId => $lesserSelectedValue));
294 $result = $this->callAPISuccess('Contact', 'Get', array('custom_' . $customId => array($op => $lesserSelectedValue)));
295 $this->assertEquals($contactId, $result['id']);
297 elseif ($op == '<') {
298 $result = $this->callAPISuccess('Contact', 'Get', array('custom_' . $customId => array($op => $selectedValue)));
299 $this->assertEquals($contactId2, $result['id']);
302 $result = $this->callAPISuccess('Contact', 'create', array('contact_type' => 'Individual', 'email' => substr(sha1(rand()), 0, 7) . 'man3@yahoo.com'));
303 $contactId3 = $result['id'];
304 $this->callAPISuccess('CustomValue', 'create', array('entity_id' => $contactId3, 'custom_' . $customId => $greaterSelectedValue));
306 $result = $this->callAPISuccess('Contact', 'Get', array('custom_' . $customId => array($op => $selectedValue)));
308 $this->assertEquals($contactId, $result['values'][$contactId]['id']);
310 $this->assertEquals($contactId3, $result['values'][$contactId3]['id']);
313 $this->assertEquals($contactId2, $result['values'][$contactId2]['id']);
315 $this->callAPISuccess('contact', 'delete', array('id' => $contactId3));
318 $this->callAPISuccess('contact', 'delete', array('id' => $contactId2));
322 $result = $this->callAPISuccess('Contact', 'Get', array('custom_' . $customId => array($op => (array) $selectedValue)));
323 $this->assertEquals($contactId, $result['id']);
327 $result = $this->callAPISuccess('Contact', 'Get', array('custom_' . $customId => array($op => (array) $notselectedValue)));
328 $this->assertEquals($contactId, $result['id']);
332 $selectedValue = is_array($selectedValue) ?
$selectedValue[0] : $selectedValue;
333 $result = $this->callAPISuccess('Contact', 'Get', array('custom_' . $customId => array($op => "%$selectedValue%")));
334 $this->assertEquals($contactId, $result['id']);
338 $result = $this->callAPISuccess('Contact', 'Get', array('custom_' . $customId => array($op => $notselectedValue)));
339 $this->assertEquals($contactId, $result['id']);
343 $result = $this->callAPISuccess('Contact', 'Get', array('custom_' . $customId => array($op => 1)));
344 $this->assertEquals(FALSE, array_key_exists($contactId, $result['values']));
348 $result = $this->callAPISuccess('Contact', 'Get', array('custom_' . $customId => array($op => 1)));
349 $this->assertEquals($contactId, $result['id']);
354 $this->callAPISuccess('Contact', 'delete', array('id' => $contactId));
358 * Ensure custom data is updated when option values are modified
360 * @link https://issues.civicrm.org/jira/browse/CRM-11856
362 * @throws \CiviCRM_API3_Exception
364 public function testAlterOptionValue() {
365 $this->_populateOptionAndCustomGroup('string');
367 $selectField = $this->customFieldCreate(array(
368 'custom_group_id' => $this->ids
['string']['custom_group_id'],
369 'label' => 'Custom Select',
370 'html_type' => 'Select',
371 'option_group_id' => $this->optionGroup
['string']['id'],
373 $selectField = civicrm_api3('customField', 'getsingle', array('id' => $selectField['id']));
374 $radioField = $this->customFieldCreate(array(
375 'custom_group_id' => $this->ids
['string']['custom_group_id'],
376 'label' => 'Custom Radio',
377 'html_type' => 'Radio',
378 'option_group_id' => $selectField['option_group_id'],
380 $multiSelectField = $this->customFieldCreate(array(
381 'custom_group_id' => $this->ids
['string']['custom_group_id'],
382 'label' => 'Custom Multi-Select',
383 'html_type' => 'Multi-Select',
384 'option_group_id' => $selectField['option_group_id'],
386 $selectName = 'custom_' . $selectField['id'];
387 $radioName = 'custom_' . $radioField['id'];
388 $multiSelectName = 'custom_' . $multiSelectField['id'];
389 $controlFieldName = 'custom_' . $this->ids
['string']['custom_field_id'];
392 'first_name' => 'abc4',
393 'last_name' => 'xyz4',
394 'contact_type' => 'Individual',
395 'email' => 'man4@yahoo.com',
396 $selectName => $this->optionGroup
['string']['values'][0],
397 $multiSelectName => $this->optionGroup
['string']['values'],
398 $radioName => $this->optionGroup
['string']['values'][1],
399 // The control group in a science experiment should be unaffected
400 $controlFieldName => $this->optionGroup
['string']['values'][2],
403 $contact = $this->callAPISuccess('Contact', 'create', $params);
405 $result = $this->callAPISuccess('Contact', 'getsingle', array(
406 'id' => $contact['id'],
407 'return' => array($selectName, $multiSelectName),
409 $this->assertEquals($params[$selectName], $result[$selectName]);
410 $this->assertEquals($params[$multiSelectName], $result[$multiSelectName]);
412 $this->callAPISuccess('OptionValue', 'create', array(
413 'value' => 'one-modified',
414 'option_group_id' => $selectField['option_group_id'],
415 'name' => 'string 1',
417 'match-mandatory' => array('option_group_id', 'name'),
421 $result = $this->callAPISuccess('Contact', 'getsingle', array(
422 'id' => $contact['id'],
423 'return' => array($selectName, $multiSelectName, $controlFieldName, $radioName),
425 // Ensure the relevant fields have been updated
426 $this->assertEquals('one-modified', $result[$selectName]);
427 $this->assertEquals(array('one-modified', $params[$radioName], $params[$controlFieldName]), $result[$multiSelectName]);
428 // This field should not have changed because we didn't alter this option
429 $this->assertEquals($params[$radioName], $result[$radioName]);
430 // This should not have changed because this field doesn't use the affected option group
431 $this->assertEquals($params[$controlFieldName], $result[$controlFieldName]);
432 // Add test of proof that multivalue fields.
433 $this->callAPISuccess('CustomValue', 'create', array(
434 'entity_id' => $contact['id'],
435 $multiSelectName => array($params[$radioName], $params[$controlFieldName]),
437 $result = $this->callAPISuccess('Contact', 'getsingle', array(
438 'id' => $contact['id'],
439 'return' => array($selectName, $multiSelectName, $controlFieldName, $radioName),
442 $this->assertEquals(array($params[$radioName], $params[$controlFieldName]), $result[$multiSelectName]);
445 public function testGettree() {
446 $cg = $this->callAPISuccess('CustomGroup', 'create', array(
447 'title' => 'TestGettree',
448 'extends' => 'Individual',
450 $cf = $this->callAPISuccess('CustomField', 'create', array(
451 'custom_group_id' => $cg['id'],
452 'label' => 'Got Options',
453 'name' => 'got_options',
454 "data_type" => "String",
455 "html_type" => "Multi-Select",
456 'option_values' => array('1' => 'One', '2' => 'Two', '3' => 'Three'),
458 $fieldName = 'custom_' . $cf['id'];
459 $contact = $this->individualCreate(array($fieldName => array('2', '3')));
461 // Verify values are formatted correctly
462 $tree = $this->callAPISuccess('CustomValue', 'gettree', array('entity_type' => 'Contact', 'entity_id' => $contact));
463 $this->assertEquals(array('2', '3'), $tree['values']['TestGettree']['fields']['got_options']['value']['data']);
464 $this->assertEquals('Two, Three', $tree['values']['TestGettree']['fields']['got_options']['value']['display']);
466 // Try limiting the return params
467 $tree = $this->callAPISuccess('CustomValue', 'gettree', array(
468 'entity_type' => 'Contact',
469 'entity_id' => $contact,
475 $this->assertEquals(array('2', '3'), $tree['values']['TestGettree']['fields']['got_options']['value']['data']);
476 $this->assertEquals('Two, Three', $tree['values']['TestGettree']['fields']['got_options']['value']['display']);
477 $this->assertEquals(array('id', 'fields'), array_keys($tree['values']['TestGettree']));
479 // Ensure display values are returned even if data is not
480 $tree = $this->callAPISuccess('CustomValue', 'gettree', array(
481 'entity_type' => 'Contact',
482 'entity_id' => $contact,
484 'custom_value.display',
487 $this->assertEquals('Two, Three', $tree['values']['TestGettree']['fields']['got_options']['value']['display']);
488 $this->assertFalse(isset($tree['values']['TestGettree']['fields']['got_options']['value']['data']));
490 // Verify that custom set appears for individuals even who don't have any custom data
491 $contact2 = $this->individualCreate();
492 $tree = $this->callAPISuccess('CustomValue', 'gettree', array('entity_type' => 'Contact', 'entity_id' => $contact2));
493 $this->assertArrayHasKey('TestGettree', $tree['values']);
495 // Verify that custom set doesn't appear for other contact types
496 $org = $this->organizationCreate();
497 $tree = $this->callAPISuccess('CustomValue', 'gettree', array('entity_type' => 'Contact', 'entity_id' => $org));
498 $this->assertArrayNotHasKey('TestGettree', $tree['values']);
502 public function testGettree_getfields() {
503 $fields = $this->callAPISuccess('CustomValue', 'getfields', array('api_action' => 'gettree'));
504 $fields = $fields['values'];
505 $this->assertTrue((bool) $fields['entity_id']['api.required']);
506 $this->assertTrue((bool) $fields['entity_type']['api.required']);
507 $this->assertEquals('custom_group.id', $fields['custom_group.id']['name']);
508 $this->assertEquals('custom_field.id', $fields['custom_field.id']['name']);
509 $this->assertEquals('custom_value.id', $fields['custom_value.id']['name']);
513 * Test that custom fields in greeting strings are updated.
515 public function testUpdateCustomGreetings() {
516 // Create a custom group with one field.
517 $customGroupResult = $this->callAPISuccess('CustomGroup', 'create', array(
519 'title' => "test custom group",
520 'extends' => "Individual",
522 $customFieldResult = $this->callAPISuccess('CustomField', 'create', array(
523 'custom_group_id' => $customGroupResult['id'],
524 'label' => "greeting test",
525 'data_type' => "String",
526 'html_type' => "Text",
528 $customFieldId = $customFieldResult['id'];
530 // Create a contact with an email greeting format that includes the new custom field.
531 $contactResult = $this->callAPISuccess('Contact', 'create', array(
532 'contact_type' => 'Individual',
533 'email' => substr(sha1(rand()), 0, 7) . '@yahoo.com',
534 'email_greeting_id' => "Customized",
535 'email_greeting_custom' => "Dear {contact.custom_{$customFieldId}}",
537 $cid = $contactResult['id'];
539 // Define testing values.
541 $testGreetingValue = "Dear $uniq";
543 // Update contact's custom field with CustomValue.create
544 $customValueResult = $this->callAPISuccess('CustomValue', 'create', array(
546 "custom_{$customFieldId}" => $uniq,
547 'entity_table' => "civicrm_contact",
550 $contact = $this->callAPISuccessGetSingle('Contact', array('id' => $cid, 'return' => 'email_greeting'));
551 $this->assertEquals($testGreetingValue, $contact['email_greeting_display']);