Merge pull request #23742 from eileenmcnaughton/import_remove
[civicrm-core.git] / CRM / Core / BAO / CustomValue.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
18/**
19 * Business objects for managing custom data values.
6a488035
TO
20 */
21class CRM_Core_BAO_CustomValue extends CRM_Core_DAO {
22
23 /**
fe482240 24 * Validate a value against a CustomField type.
6a488035 25 *
6a0b768e
TO
26 * @param string $type
27 * The type of the data.
28 * @param string $value
29 * The data to be validated.
6a488035 30 *
8d7a9d07 31 * @return bool
a6c01b45 32 * True if the value is of the specified type
6a488035
TO
33 */
34 public static function typecheck($type, $value) {
35 switch ($type) {
36 case 'Memo':
37 return TRUE;
38
39 case 'String':
40 return CRM_Utils_Rule::string($value);
41
42 case 'Int':
43 return CRM_Utils_Rule::integer($value);
44
45 case 'Float':
46 case 'Money':
47 return CRM_Utils_Rule::numeric($value);
48
49 case 'Date':
50 if (is_numeric($value)) {
51 return CRM_Utils_Rule::dateTime($value);
52 }
53 else {
54 return CRM_Utils_Rule::date($value);
55 }
56 case 'Boolean':
57 return CRM_Utils_Rule::boolean($value);
58
59 case 'ContactReference':
60 return CRM_Utils_Rule::validContact($value);
61
62 case 'StateProvince':
63
64 //fix for multi select state, CRM-3437
65 $valid = FALSE;
66 $mulValues = explode(',', $value);
67 foreach ($mulValues as $key => $state) {
68 $valid = array_key_exists(strtolower(trim($state)),
353ffa53
TO
69 array_change_key_case(array_flip(CRM_Core_PseudoConstant::stateProvinceAbbreviation()), CASE_LOWER)
70 ) || array_key_exists(strtolower(trim($state)),
71 array_change_key_case(array_flip(CRM_Core_PseudoConstant::stateProvince()), CASE_LOWER)
72 );
6a488035
TO
73 if (!$valid) {
74 break;
75 }
76 }
77 return $valid;
78
79 case 'Country':
80
81 //fix multi select country, CRM-3437
82 $valid = FALSE;
83 $mulValues = explode(',', $value);
84 foreach ($mulValues as $key => $country) {
85 $valid = array_key_exists(strtolower(trim($country)),
353ffa53
TO
86 array_change_key_case(array_flip(CRM_Core_PseudoConstant::countryIsoCode()), CASE_LOWER)
87 ) || array_key_exists(strtolower(trim($country)),
88 array_change_key_case(array_flip(CRM_Core_PseudoConstant::country()), CASE_LOWER)
89 );
6a488035
TO
90 if (!$valid) {
91 break;
92 }
93 }
94 return $valid;
95
96 case 'Link':
97 return CRM_Utils_Rule::url($value);
98 }
99 return FALSE;
100 }
101
102 /**
100fef9d 103 * Given a 'civicrm' type string, return the mysql data store area
6a488035 104 *
6a0b768e
TO
105 * @param string $type
106 * The civicrm type string.
6a488035 107 *
72b3a70c
CW
108 * @return string|null
109 * the mysql data store placeholder
6a488035
TO
110 */
111 public static function typeToField($type) {
112 switch ($type) {
113 case 'String':
114 case 'File':
115 return 'char_data';
116
117 case 'Boolean':
118 case 'Int':
119 case 'StateProvince':
120 case 'Country':
121 case 'Auto-complete':
122 return 'int_data';
123
124 case 'Float':
125 return 'float_data';
126
127 case 'Money':
128 return 'decimal_data';
129
130 case 'Memo':
131 return 'memo_data';
132
133 case 'Date':
134 return 'date_data';
135
136 case 'Link':
137 return 'char_data';
138
139 default:
140 return NULL;
141 }
142 }
143
b5c2afd0 144 /**
72b3a70c
CW
145 * @param array $formValues
146 * @return null
b5c2afd0 147 */
c94d39fd 148 public static function fixCustomFieldValue(&$formValues) {
6a488035
TO
149 if (empty($formValues)) {
150 return NULL;
151 }
152 foreach (array_keys($formValues) as $key) {
153 if (substr($key, 0, 7) != 'custom_') {
154 continue;
155 }
156 elseif (empty($formValues[$key])) {
157 continue;
158 }
159
160 $htmlType = CRM_Core_DAO::getFieldValue('CRM_Core_BAO_CustomField',
161 substr($key, 7), 'html_type'
162 );
c94d39fd 163 $dataType = CRM_Core_DAO::getFieldValue('CRM_Core_BAO_CustomField',
164 substr($key, 7), 'data_type'
165 );
166
167 if (is_array($formValues[$key])) {
d7e0081e 168 if (!in_array(key($formValues[$key]), CRM_Core_DAO::acceptedSQLOperators(), TRUE)) {
be2fb01f 169 $formValues[$key] = ['IN' => $formValues[$key]];
c94d39fd 170 }
171 }
172 elseif (($htmlType == 'TextArea' ||
173 ($htmlType == 'Text' && $dataType == 'String')
2b6760c5 174 ) && strstr($formValues[$key], '%')
6a488035 175 ) {
be2fb01f 176 $formValues[$key] = ['LIKE' => $formValues[$key]];
6a488035 177 }
71dfa06c
MD
178 elseif ($htmlType == 'Autocomplete-Select' && !empty($formValues[$key]) && is_string($formValues[$key]) && (strpos($formValues[$key], ',') != FALSE)) {
179 $formValues[$key] = ['IN' => explode(',', $formValues[$key])];
180 }
6a488035
TO
181 }
182 }
183
184 /**
fe482240 185 * Delete option value give an option value and custom group id.
6a488035 186 *
6a0b768e
TO
187 * @param int $customValueID
188 * Custom value ID.
189 * @param int $customGroupID
190 * Custom group ID.
6a488035 191 */
00be9182 192 public static function deleteCustomValue($customValueID, $customGroupID) {
6a488035
TO
193 // first we need to find custom value table, from custom group ID
194 $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupID, 'table_name');
195
2c57d73b 196 // Retrieve the $entityId so we can pass that to the hook.
445bbeed 197 $entityID = (int) CRM_Core_DAO::singleValueQuery("SELECT entity_id FROM {$tableName} WHERE id = %1", [
be2fb01f
CW
198 1 => [$customValueID, 'Integer'],
199 ]);
2c57d73b 200
6a488035
TO
201 // delete custom value from corresponding custom value table
202 $sql = "DELETE FROM {$tableName} WHERE id = {$customValueID}";
203 CRM_Core_DAO::executeQuery($sql);
204
205 CRM_Utils_Hook::custom('delete',
445bbeed 206 (int) $customGroupID,
2c57d73b 207 $entityID,
6a488035
TO
208 $customValueID
209 );
210 }
96025800 211
5e327f37
CW
212 /**
213 * ACL clause for an APIv4 custom pseudo-entity (aka multi-record custom group extending Contact).
214 * @return array
215 */
216 public function addSelectWhereClause() {
217 $clauses = [
218 'entity_id' => CRM_Utils_SQL::mergeSubquery('Contact'),
219 ];
220 CRM_Utils_Hook::selectWhereClause($this, $clauses);
221 return $clauses;
222 }
223
01c65aab
CW
224 /**
225 * Special checkAccess function for multi-record custom pseudo-entities
226 *
a5d0f31a
TO
227 * @param string $entityName
228 * Ex: 'Contact' or 'Custom_Foobar'
01c65aab
CW
229 * @param string $action
230 * @param array $record
70da3927
TO
231 * @param int $userID
232 * Contact ID of the active user (whose access we must check). 0 for anonymous.
01c65aab 233 * @return bool
e294cebf 234 * TRUE if granted. FALSE if prohibited. NULL if indeterminate.
01c65aab 235 */
70da3927 236 public static function _checkAccess(string $entityName, string $action, array $record, int $userID): ?bool {
b87406e2
TO
237 // This check implements two rules: you must have access to the specific custom-data-group - and to the underlying record (e.g. Contact).
238
a5d0f31a 239 $groupName = substr($entityName, 0, 7) === 'Custom_' ? substr($entityName, 7) : NULL;
b87406e2
TO
240 $extends = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $groupName, 'extends', 'name');
241 $id = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $groupName, 'id', 'name');
01c65aab
CW
242 if (!$groupName) {
243 // $groupName is required but the function signature has to match the parent.
b87406e2 244 throw new CRM_Core_Exception('Missing required group-name in CustomValue::checkAccess');
01c65aab 245 }
b87406e2
TO
246
247 if (empty($extends) || empty($id)) {
248 throw new CRM_Core_Exception('Received invalid group-name in CustomValue::checkAccess');
6ea81ac6
TO
249 }
250
317103ab
CW
251 $actionType = $action === 'get' ? CRM_Core_Permission::VIEW : CRM_Core_Permission::EDIT;
252 if (!\CRM_Core_BAO_CustomGroup::checkGroupAccess($id, $actionType, $userID)) {
b87406e2
TO
253 return FALSE;
254 }
255
256 $eid = $record['entity_id'] ?? NULL;
257 if (!$eid) {
01c65aab 258 $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $groupName, 'table_name', 'name');
b87406e2 259 $eid = CRM_Core_DAO::singleValueQuery("SELECT entity_id FROM `$tableName` WHERE id = " . (int) $record['id']);
01c65aab 260 }
01c65aab 261
b87406e2 262 // Do we have access to the target record?
f1b78592 263 if ($extends === 'Contact' || in_array($extends, CRM_Contact_BAO_ContactType::basicTypes(TRUE), TRUE)) {
b87406e2
TO
264 return \Civi\Api4\Utils\CoreUtil::checkAccessDelegated('Contact', 'update', ['id' => $eid], $userID);
265 }
266 elseif (\Civi\Api4\Utils\CoreUtil::getApiClass($extends)) {
267 // For most entities (Activity, Relationship, Contribution, ad nauseum), we acn just use an eponymous API.
268 return \Civi\Api4\Utils\CoreUtil::checkAccessDelegated($extends, 'update', ['id' => $eid], $userID);
269 }
270 else {
271 // Do you need to add a special case for some oddball custom-group type?
272 throw new CRM_Core_Exception("Cannot assess delegated permissions for group {$groupName}.");
273 }
01c65aab
CW
274 }
275
06508628 276}