Merge pull request #21383 from eileenmcnaughton/case_tokens
[civicrm-core.git] / tests / phpunit / api / v4 / Action / BasicCustomFieldTest.php
1 <?php
2
3 /*
4 +--------------------------------------------------------------------+
5 | Copyright CiviCRM LLC. All rights reserved. |
6 | |
7 | This work is published under the GNU AGPLv3 license with some |
8 | permitted exceptions and without any warranty. For full license |
9 | and copyright information, see https://civicrm.org/licensing |
10 +--------------------------------------------------------------------+
11 */
12
13 /**
14 *
15 * @package CRM
16 * @copyright CiviCRM LLC https://civicrm.org/licensing
17 */
18
19
20 namespace api\v4\Action;
21
22 use Civi\Api4\Contact;
23 use Civi\Api4\CustomField;
24 use Civi\Api4\CustomGroup;
25 use Civi\Api4\OptionGroup;
26 use Civi\Api4\Relationship;
27 use Civi\Api4\RelationshipCache;
28
29 /**
30 * @group headless
31 */
32 class BasicCustomFieldTest extends BaseCustomValueTest {
33
34 /**
35 * @throws \API_Exception
36 */
37 public function testWithSingleField(): void {
38 $customGroup = CustomGroup::create(FALSE)
39 ->addValue('name', 'MyIndividualFields')
40 ->addValue('extends', 'Individual')
41 ->execute()
42 ->first();
43
44 CustomField::create(FALSE)
45 ->addValue('label', 'FavColor')
46 ->addValue('custom_group_id', $customGroup['id'])
47 ->addValue('html_type', 'Text')
48 ->addValue('data_type', 'String')
49 ->execute();
50
51 // Individual fields should show up when contact_type = null|Individual but not other contact types
52 $getFields = Contact::getFields(FALSE);
53 $this->assertEquals('Custom', $getFields->execute()->indexBy('name')['MyIndividualFields.FavColor']['type']);
54 $this->assertContains('MyIndividualFields.FavColor', $getFields->setValues(['contact_type' => 'Individual'])->execute()->column('name'));
55 $this->assertNotContains('MyIndividualFields.FavColor', $getFields->setValues(['contact_type' => 'Household'])->execute()->column('name'));
56
57 $contactId = Contact::create(FALSE)
58 ->addValue('first_name', 'Johann')
59 ->addValue('last_name', 'Tester')
60 ->addValue('contact_type', 'Individual')
61 ->addValue('MyIndividualFields.FavColor', 'Red')
62 ->execute()
63 ->first()['id'];
64
65 $contact = Contact::get(FALSE)
66 ->addSelect('first_name')
67 ->addSelect('MyIndividualFields.FavColor')
68 ->addWhere('id', '=', $contactId)
69 ->addWhere('MyIndividualFields.FavColor', '=', 'Red')
70 ->execute()
71 ->first();
72
73 $this->assertEquals('Red', $contact['MyIndividualFields.FavColor']);
74
75 Contact::update()
76 ->addWhere('id', '=', $contactId)
77 ->addValue('MyIndividualFields.FavColor', 'Blue')
78 ->execute();
79
80 $contact = Contact::get(FALSE)
81 ->addSelect('MyIndividualFields.FavColor')
82 ->addWhere('id', '=', $contactId)
83 ->execute()
84 ->first();
85
86 $this->assertEquals('Blue', $contact['MyIndividualFields.FavColor']);
87 }
88
89 public function testWithTwoFields() {
90 $optionGroupCount = OptionGroup::get(FALSE)->selectRowCount()->execute()->count();
91
92 // First custom set
93 CustomGroup::create(FALSE)
94 ->addValue('name', 'MyContactFields')
95 ->addValue('extends', 'Contact')
96 ->addChain('field1', CustomField::create()
97 ->addValue('label', 'FavColor')
98 ->addValue('custom_group_id', '$id')
99 ->addValue('html_type', 'Text')
100 ->addValue('data_type', 'String'))
101 ->addChain('field2', CustomField::create()
102 ->addValue('label', 'FavFood')
103 ->addValue('custom_group_id', '$id')
104 ->addValue('html_type', 'Text')
105 ->addValue('data_type', 'String'))
106 ->execute();
107
108 // Second custom set
109 CustomGroup::create(FALSE)
110 ->addValue('name', 'MyContactFields2')
111 ->addValue('extends', 'Contact')
112 ->addChain('field1', CustomField::create()
113 ->addValue('label', 'FavColor')
114 ->addValue('custom_group_id', '$id')
115 ->addValue('html_type', 'Text')
116 ->addValue('data_type', 'String'))
117 ->addChain('field2', CustomField::create()
118 ->addValue('label', 'FavFood')
119 ->addValue('custom_group_id', '$id')
120 ->addValue('html_type', 'Text')
121 ->addValue('data_type', 'String'))
122 ->execute();
123
124 // Test that no new option groups have been created (these are text fields with no options)
125 $this->assertEquals($optionGroupCount, OptionGroup::get(FALSE)->selectRowCount()->execute()->count());
126
127 $contactId1 = Contact::create(FALSE)
128 ->addValue('first_name', 'Johann')
129 ->addValue('last_name', 'Tester')
130 ->addValue('MyContactFields.FavColor', 'Red')
131 ->addValue('MyContactFields.FavFood', 'Cherry')
132 ->execute()
133 ->first()['id'];
134
135 $contactId2 = Contact::create(FALSE)
136 ->addValue('first_name', 'MaryLou')
137 ->addValue('last_name', 'Tester')
138 ->addValue('MyContactFields.FavColor', 'Purple')
139 ->addValue('MyContactFields.FavFood', 'Grapes')
140 ->execute()
141 ->first()['id'];
142
143 $contact = Contact::get(FALSE)
144 ->addSelect('first_name')
145 ->addSelect('MyContactFields.FavColor')
146 ->addSelect('MyContactFields.FavFood')
147 ->addWhere('id', '=', $contactId1)
148 ->addWhere('MyContactFields.FavColor', '=', 'Red')
149 ->addWhere('MyContactFields.FavFood', '=', 'Cherry')
150 ->execute()
151 ->first();
152 $this->assertArrayHasKey('MyContactFields.FavColor', $contact);
153 $this->assertEquals('Red', $contact['MyContactFields.FavColor']);
154
155 // By default custom fields are not returned
156 $contact = Contact::get(FALSE)
157 ->addWhere('id', '=', $contactId1)
158 ->addWhere('MyContactFields.FavColor', '=', 'Red')
159 ->addWhere('MyContactFields.FavFood', '=', 'Cherry')
160 ->execute()
161 ->first();
162 $this->assertArrayNotHasKey('MyContactFields.FavColor', $contact);
163
164 // Update 2nd set and ensure 1st hasn't changed
165 Contact::update()
166 ->addWhere('id', '=', $contactId1)
167 ->addValue('MyContactFields2.FavColor', 'Orange')
168 ->addValue('MyContactFields2.FavFood', 'Tangerine')
169 ->execute();
170 $contact = Contact::get(FALSE)
171 ->addSelect('MyContactFields.FavColor', 'MyContactFields2.FavColor', 'MyContactFields.FavFood', 'MyContactFields2.FavFood')
172 ->addWhere('id', '=', $contactId1)
173 ->execute()
174 ->first();
175 $this->assertEquals('Red', $contact['MyContactFields.FavColor']);
176 $this->assertEquals('Orange', $contact['MyContactFields2.FavColor']);
177 $this->assertEquals('Cherry', $contact['MyContactFields.FavFood']);
178 $this->assertEquals('Tangerine', $contact['MyContactFields2.FavFood']);
179
180 // Update 1st set and ensure 2st hasn't changed
181 Contact::update()
182 ->addWhere('id', '=', $contactId1)
183 ->addValue('MyContactFields.FavColor', 'Blue')
184 ->execute();
185 $contact = Contact::get(FALSE)
186 ->addSelect('custom.*')
187 ->addWhere('id', '=', $contactId1)
188 ->execute()
189 ->first();
190 $this->assertEquals('Blue', $contact['MyContactFields.FavColor']);
191 $this->assertEquals('Orange', $contact['MyContactFields2.FavColor']);
192 $this->assertEquals('Cherry', $contact['MyContactFields.FavFood']);
193 $this->assertEquals('Tangerine', $contact['MyContactFields2.FavFood']);
194
195 $search = Contact::get(FALSE)
196 ->addClause('OR', ['MyContactFields.FavColor', '=', 'Blue'], ['MyContactFields.FavFood', '=', 'Grapes'])
197 ->addSelect('id')
198 ->addOrderBy('id')
199 ->execute()
200 ->indexBy('id');
201
202 $this->assertEquals([$contactId1, $contactId2], array_keys((array) $search));
203
204 $search = Contact::get(FALSE)
205 ->addClause('NOT', ['MyContactFields.FavColor', '=', 'Purple'], ['MyContactFields.FavFood', '=', 'Grapes'])
206 ->addSelect('id')
207 ->addOrderBy('id')
208 ->execute()
209 ->indexBy('id');
210
211 $this->assertNotContains($contactId2, array_keys((array) $search));
212
213 $search = Contact::get(FALSE)
214 ->addClause('NOT', ['MyContactFields.FavColor', '=', 'Purple'], ['MyContactFields.FavFood', '=', 'Grapes'])
215 ->addSelect('id')
216 ->addOrderBy('id')
217 ->execute()
218 ->indexBy('id');
219
220 $this->assertContains($contactId1, array_keys((array) $search));
221 $this->assertNotContains($contactId2, array_keys((array) $search));
222
223 $search = Contact::get(FALSE)
224 ->setWhere([['NOT', ['OR', [['MyContactFields.FavColor', '=', 'Blue'], ['MyContactFields.FavFood', '=', 'Grapes']]]]])
225 ->addSelect('id')
226 ->addOrderBy('id')
227 ->execute()
228 ->indexBy('id');
229
230 $this->assertNotContains($contactId1, array_keys((array) $search));
231 $this->assertNotContains($contactId2, array_keys((array) $search));
232 }
233
234 public function testRelationshipCacheCustomFields() {
235 $cgName = uniqid('RelFields');
236
237 $customGroup = CustomGroup::create(FALSE)
238 ->addValue('name', $cgName)
239 ->addValue('extends', 'Relationship')
240 ->execute()
241 ->first();
242
243 CustomField::create(FALSE)
244 ->addValue('label', 'PetName')
245 ->addValue('custom_group_id', $customGroup['id'])
246 ->addValue('html_type', 'Text')
247 ->addValue('data_type', 'String')
248 ->execute();
249
250 // Adding custom field to Relationship entity also adds it to RelationshipCache entity
251 $this->assertCount(1, RelationshipCache::getFields(FALSE)
252 ->addWhere('name', '=', "$cgName.PetName")
253 ->execute()
254 );
255
256 $parent = Contact::create(FALSE)
257 ->addValue('first_name', 'Parent')
258 ->addValue('last_name', 'Tester')
259 ->addValue('contact_type', 'Individual')
260 ->execute()
261 ->first()['id'];
262
263 $child = Contact::create(FALSE)
264 ->addValue('first_name', 'Child')
265 ->addValue('last_name', 'Tester')
266 ->addValue('contact_type', 'Individual')
267 ->execute()
268 ->first()['id'];
269
270 Relationship::create(FALSE)
271 ->addValue('contact_id_a', $parent)
272 ->addValue('contact_id_b', $child)
273 ->addValue('relationship_type_id', 1)
274 ->addValue("$cgName.PetName", 'Buddy')
275 ->execute();
276
277 // Test get directly from relationshipCache entity
278 $results = RelationshipCache::get(FALSE)
279 ->addSelect("$cgName.PetName")
280 ->addWhere("$cgName.PetName", '=', 'Buddy')
281 ->execute();
282
283 $this->assertCount(2, $results);
284 $this->assertEquals('Buddy', $results[0]["$cgName.PetName"]);
285
286 // Test get via bridge INNER join
287 $result = Contact::get(FALSE)
288 ->addSelect('relative.display_name', "relative.$cgName.PetName")
289 ->addJoin('Contact AS relative', 'INNER', 'RelationshipCache')
290 ->addWhere('id', '=', $parent)
291 ->addWhere('relative.relationship_type_id', '=', 1)
292 ->execute()->single();
293 $this->assertEquals('Child Tester', $result['relative.display_name']);
294 $this->assertEquals('Buddy', $result["relative.$cgName.PetName"]);
295
296 // Test get via bridge LEFT join
297 $result = Contact::get(FALSE)
298 ->addSelect('relative.display_name', "relative.$cgName.PetName")
299 ->addJoin('Contact AS relative', 'LEFT', 'RelationshipCache')
300 ->addWhere('id', '=', $parent)
301 ->addWhere('relative.relationship_type_id', '=', 1)
302 ->execute()->single();
303 $this->assertEquals('Child Tester', $result['relative.display_name']);
304 $this->assertEquals('Buddy', $result["relative.$cgName.PetName"]);
305 }
306
307 public function testMultipleJoinsToCustomTable() {
308 $cgName = uniqid('My');
309
310 CustomGroup::create(FALSE)
311 ->addValue('name', $cgName)
312 ->addValue('extends', 'Contact')
313 ->addChain('field1', CustomField::create()
314 ->addValue('label', 'FavColor')
315 ->addValue('custom_group_id', '$id')
316 ->addValue('html_type', 'Text')
317 ->addValue('data_type', 'String'))
318 ->execute();
319
320 $parent = Contact::create(FALSE)
321 ->addValue('first_name', 'Parent')
322 ->addValue('last_name', 'Tester')
323 ->addValue("$cgName.FavColor", 'Purple')
324 ->execute()
325 ->first()['id'];
326
327 $child = Contact::create(FALSE)
328 ->addValue('first_name', 'Child')
329 ->addValue('last_name', 'Tester')
330 ->addValue("$cgName.FavColor", 'Cyan')
331 ->execute()
332 ->first()['id'];
333
334 Relationship::create(FALSE)
335 ->addValue('contact_id_a', $parent)
336 ->addValue('contact_id_b', $child)
337 ->addValue('relationship_type_id', 1)
338 ->execute();
339
340 $results = Contact::get(FALSE)
341 ->addSelect('first_name', 'child.first_name', "$cgName.FavColor", "child.$cgName.FavColor")
342 ->addWhere('id', '=', $parent)
343 ->addJoin('Contact AS child', 'INNER', 'RelationshipCache', ['id', '=', 'child.far_contact_id'])
344 ->execute();
345
346 $this->assertCount(1, $results);
347 $this->assertEquals('Parent', $results[0]['first_name']);
348 $this->assertEquals('Child', $results[0]['child.first_name']);
349 $this->assertEquals('Purple', $results[0]["$cgName.FavColor"]);
350 $this->assertEquals('Cyan', $results[0]["child.$cgName.FavColor"]);
351 }
352
353 /**
354 * Some types are creating a dummy option group even if we don't have
355 * any option values.
356 * @throws \API_Exception
357 */
358 public function testUndesiredOptionGroupCreation(): void {
359 $optionGroupCount = OptionGroup::get(FALSE)->selectRowCount()->execute()->count();
360
361 $customGroup = CustomGroup::create(FALSE)
362 ->addValue('name', 'MyIndividualFields')
363 ->addValue('extends', 'Individual')
364 ->execute()
365 ->first();
366
367 // This one doesn't make sense to have an option group.
368 CustomField::create(FALSE)
369 ->addValue('label', 'FavColor')
370 ->addValue('custom_group_id', $customGroup['id'])
371 ->addValue('html_type', 'Number')
372 ->addValue('data_type', 'Money')
373 ->execute();
374
375 // This one might be ok if we planned to then use the autocreated option
376 // group, but if we go on to create our own after then we have an extra
377 // unused group.
378 CustomField::create(FALSE)
379 ->addValue('label', 'FavMovie')
380 ->addValue('custom_group_id', $customGroup['id'])
381 ->addValue('html_type', 'Select')
382 ->addValue('data_type', 'String')
383 ->execute();
384
385 $this->assertEquals($optionGroupCount, OptionGroup::get(FALSE)->selectRowCount()->execute()->count());
386 }
387
388 }