3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2014
35 class CRM_Core_BAO_CustomValueTable
{
38 * @param array $customParams
42 public static function create(&$customParams) {
43 if (empty($customParams) ||
44 !is_array($customParams)
49 foreach ($customParams as $tableName => $tables) {
50 foreach ($tables as $index => $fields) {
59 foreach ($fields as $field) {
61 $entityID = $field['entity_id'];
62 $hookID = $field['custom_group_id'];
63 $isMultiple = $field['is_multiple'];
64 if (array_key_exists('id', $field)) {
65 $sqlOP = "UPDATE $tableName ";
66 $where = " WHERE id = %{$count}";
67 $params[$count] = array($field['id'], 'Integer');
72 $sqlOP = "INSERT INTO $tableName ";
78 // fix the value before we store it
79 $value = $field['value'];
80 $type = $field['type'];
84 if (is_array($value)) {
85 $value = CRM_Core_DAO
::VALUE_SEPARATOR
. implode(CRM_Core_DAO
::VALUE_SEPARATOR
, $value) . CRM_Core_DAO
::VALUE_SEPARATOR
;
88 elseif (!is_numeric($value)) {
89 //fix for multi select state, CRM-3437
90 $mulValues = explode(',', $value);
91 $validStates = array();
92 foreach ($mulValues as $key => $stateVal) {
94 $states['state_province'] = trim($stateVal);
96 CRM_Utils_Array
::lookupValue($states, 'state_province',
97 CRM_Core_PseudoConstant
::stateProvince(), TRUE
99 if (empty($states['state_province_id'])) {
100 CRM_Utils_Array
::lookupValue($states, 'state_province',
101 CRM_Core_PseudoConstant
::stateProvinceAbbreviation(), TRUE
104 $validStates[] = $states['state_province_id'];
106 $value = implode(CRM_Core_DAO
::VALUE_SEPARATOR
,
113 // using type of timestamp allows us to sneak in a null into db
114 // gross but effective hack
122 if (is_array($value)) {
123 $value = CRM_Core_DAO
::VALUE_SEPARATOR
. implode(CRM_Core_DAO
::VALUE_SEPARATOR
, $value) . CRM_Core_DAO
::VALUE_SEPARATOR
;
126 elseif (!is_numeric($value)) {
127 //fix for multi select country, CRM-3437
128 $mulValues = explode(',', $value);
129 $validCountries = array();
130 foreach ($mulValues as $key => $countryVal) {
131 $countries = array();
132 $countries['country'] = trim($countryVal);
133 CRM_Utils_Array
::lookupValue($countries, 'country',
134 CRM_Core_PseudoConstant
::country(), TRUE
136 if (empty($countries['country_id'])) {
137 CRM_Utils_Array
::lookupValue($countries, 'country',
138 CRM_Core_PseudoConstant
::countryIsoCode(), TRUE
141 $validCountries[] = $countries['country_id'];
143 $value = implode(CRM_Core_DAO
::VALUE_SEPARATOR
,
150 // using type of timestamp allows us to sneak in a null into db
151 // gross but effective hack
158 if (!$field['file_id']) {
159 CRM_Core_Error
::fatal();
162 // need to add/update civicrm_entity_file
163 $entityFileDAO = new CRM_Core_DAO_EntityFile();
164 $entityFileDAO->file_id
= $field['file_id'];
165 $entityFileDAO->find(TRUE);
167 $entityFileDAO->entity_table
= $field['table_name'];
168 $entityFileDAO->entity_id
= $field['entity_id'];
169 $entityFileDAO->file_id
= $field['file_id'];
170 $entityFileDAO->save();
171 $entityFileDAO->free();
172 $value = $field['file_id'];
177 $value = CRM_Utils_Date
::isoToMysql($value);
181 if (is_numeric($value)) {
189 case 'ContactReference':
190 if ($value == NULL) {
198 case 'RichTextEditor':
204 $value = CRM_Utils_String
::strtoboolstr($value);
205 if ($value === FALSE) {
213 $set[$field['column_name']] = "%{$count}";
214 $params[$count] = array($value, $type);
219 $setClause = array();
220 foreach ($set as $n => $v) {
221 $setClause[] = "$n = $v";
223 $setClause = implode(',', $setClause);
225 // do this only for insert
226 $set['entity_id'] = "%{$count}";
227 $params[$count] = array($entityID, 'Integer');
230 $fieldNames = implode(',', array_keys($set));
231 $fieldValues = implode(',', array_values($set));
232 $query = "$sqlOP ( $fieldNames ) VALUES ( $fieldValues )";
233 // for multiple values we dont do on duplicate key update
235 $query .= " ON DUPLICATE KEY UPDATE $setClause";
239 $query = "$sqlOP SET $setClause $where";
241 $dao = CRM_Core_DAO
::executeQuery($query, $params);
243 CRM_Utils_Hook
::custom($hookOP,
254 * Given a field return the mysql data type associated with it
256 * @param string $type
257 * The civicrm type string.
259 * @param int $maxLength
261 * @return the mysql data store placeholder
264 public static function fieldToSQLType($type, $maxLength = 255) {
265 if (!isset($maxLength) ||
266 !is_numeric($maxLength) ||
275 return "varchar($maxLength)";
283 // the below three are FK's, and have constraints added to them
285 case 'ContactReference':
286 case 'StateProvince':
289 return 'int unsigned';
295 return 'decimal(20,2)';
298 case 'RichTextEditor':
305 CRM_Core_Error
::fatal();
310 * @param array $params
311 * @param $entityTable
312 * @param int $entityID
314 public static function store(&$params, $entityTable, $entityID) {
316 foreach ($params as $fieldID => $param) {
317 foreach ($param as $index => $customValue) {
319 'entity_table' => $entityTable,
320 'entity_id' => $entityID,
321 'value' => $customValue['value'],
322 'type' => $customValue['type'],
323 'custom_field_id' => $customValue['custom_field_id'],
324 'custom_group_id' => $customValue['custom_group_id'],
325 'table_name' => $customValue['table_name'],
326 'column_name' => $customValue['column_name'],
327 'is_multiple' => CRM_Utils_Array
::value('is_multiple', $customValue),
328 'file_id' => $customValue['file_id'],
331 // fix Date type to be timestamp, since that is how we store in db
332 if ($cvParam['type'] == 'Date') {
333 $cvParam['type'] = 'Timestamp';
336 if (!empty($customValue['id'])) {
337 $cvParam['id'] = $customValue['id'];
339 if (!array_key_exists($customValue['table_name'], $cvParams)) {
340 $cvParams[$customValue['table_name']] = array();
343 if (!array_key_exists($index, $cvParams[$customValue['table_name']])) {
344 $cvParams[$customValue['table_name']][$index] = array();
347 $cvParams[$customValue['table_name']][$index][] = $cvParam;
350 if (!empty($cvParams)) {
351 self
::create($cvParams);
356 * @param array $params
357 * @param $customFields
358 * @param $entityTable
359 * @param int $entityID
360 * @param $customFieldExtends
362 public static function postProcess(&$params, &$customFields, $entityTable, $entityID, $customFieldExtends) {
363 $customData = CRM_Core_BAO_CustomField
::postProcess($params,
369 if (!empty($customData)) {
370 self
::store($customData, $entityTable, $entityID);
375 * Return an array of all custom values associated with an entity.
377 * @param int $entityID
378 * Identification number of the entity.
379 * @param string $entityType
380 * Type of entity that the entityID corresponds to, specified.
381 * as a string with format "'<EntityName>'". Comma separated
382 * list may be used to specify OR matches. Allowable values
383 * are enumerated types in civicrm_custom_group.extends field.
384 * Optional. Default value assumes entityID references a
386 * @param array $fieldIDs
387 * Optional list of fieldIDs that we want to retrieve. If this.
388 * is set the entityType is ignored
390 * @param bool $formatMultiRecordField
392 * @return array $fields Array of custom values for the entity with key=>value
393 * pairs specified as civicrm_custom_field.id => custom value.
394 * Empty array if no custom values found.
397 public static function &getEntityValues($entityID, $entityType = NULL, $fieldIDs = NULL, $formatMultiRecordField = FALSE) {
399 // adding this here since an empty contact id could have serious repurcussions
400 // like looping forever
401 CRM_Core_Error
::fatal('Please file an issue with the backtrace');
407 $cond[] = "cg.extends IN ( '$entityType' )";
412 $fieldIDList = implode(',', $fieldIDs);
413 $cond[] = "cf.id IN ( $fieldIDList )";
416 $cond[] = "cg.extends IN ( 'Contact', 'Individual', 'Household', 'Organization' )";
418 $cond = implode(' AND ', $cond);
420 // first find all the fields that extend this type of entity
422 SELECT cg.table_name,
427 cf.data_type as fieldDataType
428 FROM civicrm_custom_group cg,
429 civicrm_custom_field cf
430 WHERE cf.custom_group_id = cg.id
435 $dao = CRM_Core_DAO
::executeQuery($query);
437 $select = $fields = $isMultiple = array();
439 while ($dao->fetch()) {
440 if (!array_key_exists($dao->table_name
, $select)) {
441 $fields[$dao->table_name
] = array();
442 $select[$dao->table_name
] = array();
444 $fields[$dao->table_name
][] = $dao->fieldID
;
445 $select[$dao->table_name
][] = "{$dao->column_name} AS custom_{$dao->fieldID}";
446 $isMultiple[$dao->table_name
] = $dao->is_multiple ?
TRUE : FALSE;
447 $file[$dao->table_name
][$dao->fieldID
] = $dao->fieldDataType
;
451 foreach ($select as $tableName => $clauses) {
452 $query = "SELECT id, " . implode(', ', $clauses) . " FROM $tableName WHERE entity_id = $entityID";
453 $dao = CRM_Core_DAO
::executeQuery($query);
454 while ($dao->fetch()) {
455 foreach ($fields[$tableName] as $fieldID) {
456 $fieldName = "custom_{$fieldID}";
457 if ($isMultiple[$tableName]) {
458 if ($formatMultiRecordField) {
459 if ($file[$tableName][$fieldID] == 'File') {
460 if ($fileid = $dao->$fieldName) {
461 $fileurl = CRM_Core_BAO_File
::paperIconAttachment($tableName, $entityID);
462 $result["{$dao->id}"]["{$fieldID}"] = $fileurl[$dao->$fieldName];
466 $result["{$dao->id}"]["{$fieldID}"] = $dao->$fieldName;
470 $result["{$fieldID}_{$dao->id}"] = $dao->$fieldName;
474 if ($file[$tableName][$fieldID] == 'File') {
475 if ($fileid = $dao->$fieldName) {
476 $fileurl = CRM_Core_BAO_File
::paperIconAttachment($tableName, $entityID);
477 $result[$fieldID] = $fileurl[$dao->$fieldName];
481 $result[$fieldID] = $dao->$fieldName;
491 * Take in an array of entityID, custom_XXX => value
492 * and set the value in the appropriate table. Should also be able
493 * to set the value to null. Follows api parameter/return conventions
497 * @param array $params
503 public static function setValues(&$params) {
505 if (!isset($params['entityID']) ||
506 CRM_Utils_Type
::escape($params['entityID'], 'Integer', FALSE) === NULL
508 return CRM_Core_Error
::createAPIError(ts('entityID needs to be set and of type Integer'));
511 // first collect all the id/value pairs. The format is:
512 // custom_X => value or custom_X_VALUEID => value (for multiple values), VALUEID == -1, -2 etc for new insertions
514 $fieldValues = array();
515 foreach ($params as $n => $v) {
516 if ($customFieldInfo = CRM_Core_BAO_CustomField
::getKeyID($n, TRUE)) {
517 $fieldID = (int ) $customFieldInfo[0];
518 if (CRM_Utils_Type
::escape($fieldID, 'Integer', FALSE) === NULL) {
519 return CRM_Core_Error
::createAPIError(ts('field ID needs to be of type Integer for index %1',
523 if (!array_key_exists($fieldID, $fieldValues)) {
524 $fieldValues[$fieldID] = array();
527 if ($customFieldInfo[1]) {
528 $id = (int ) $customFieldInfo[1];
530 $fieldValues[$fieldID][] = array(
537 $fieldIDList = implode(',', array_keys($fieldValues));
539 // format it so that we can just use create
541 SELECT cg.table_name as table_name ,
543 cg.is_multiple as is_multiple,
544 cf.column_name as column_name,
546 cf.data_type as data_type
547 FROM civicrm_custom_group cg,
548 civicrm_custom_field cf
549 WHERE cf.custom_group_id = cg.id
550 AND cf.id IN ( $fieldIDList )
553 $dao = CRM_Core_DAO
::executeQuery($sql);
556 while ($dao->fetch()) {
557 $dataType = $dao->data_type
== 'Date' ?
'Timestamp' : $dao->data_type
;
558 foreach ($fieldValues[$dao->cf_id
] as $fieldValue) {
559 // Format null values correctly
560 if ($fieldValue['value'] === NULL ||
$fieldValue['value'] === '') {
566 $fieldValue['value'] = '';
570 $fieldValue['value'] = NULL;
573 case 'StateProvince':
577 $fieldValue['value'] = (int) 0;
581 // Ensure that value is of the right data type
582 elseif (CRM_Utils_Type
::escape($fieldValue['value'], $dataType, FALSE) === NULL) {
583 return CRM_Core_Error
::createAPIError(ts('value: %1 is not of the right field data type: %2',
585 1 => $fieldValue['value'],
586 2 => $dao->data_type
,
592 'entity_id' => $params['entityID'],
593 'value' => $fieldValue['value'],
595 'custom_field_id' => $dao->cf_id
,
596 'custom_group_id' => $dao->cg_id
,
597 'table_name' => $dao->table_name
,
598 'column_name' => $dao->column_name
,
599 'is_multiple' => $dao->is_multiple
,
602 if ($cvParam['type'] == 'File') {
603 $cvParam['file_id'] = $fieldValue['value'];
606 if (!array_key_exists($dao->table_name
, $cvParams)) {
607 $cvParams[$dao->table_name
] = array();
610 if (!array_key_exists($fieldValue['id'], $cvParams[$dao->table_name
])) {
611 $cvParams[$dao->table_name
][$fieldValue['id']] = array();
614 if ($fieldValue['id'] > 0) {
615 $cvParam['id'] = $fieldValue['id'];
617 $cvParams[$dao->table_name
][$fieldValue['id']][] = $cvParam;
621 if (!empty($cvParams)) {
622 self
::create($cvParams);
623 return array('is_error' => 0, 'result' => 1);
626 return CRM_Core_Error
::createAPIError(ts('Unknown error'));
630 * Take in an array of entityID, custom_ID
631 * and gets the value from the appropriate table.
633 * To get the values of custom fields with IDs 13 and 43 for contact ID 1327, use:
634 * $params = array( 'entityID' => 1327, 'custom_13' => 1, 'custom_43' => 1 );
636 * Entity Type will be infered by the custom fields you request
637 * Specify $params['entityType'] if you do not supply any custom fields to return
638 * and entity type is other than Contact
642 * @param array $params
648 public static function &getValues(&$params) {
649 if (empty($params)) {
652 if (!isset($params['entityID']) ||
653 CRM_Utils_Type
::escape($params['entityID'],
657 return CRM_Core_Error
::createAPIError(ts('entityID needs to be set and of type Integer'));
660 // first collect all the ids. The format is:
663 foreach ($params as $n => $v) {
665 if (substr($n, 0, 7) == 'custom_') {
666 $idx = substr($n, 7);
667 if (CRM_Utils_Type
::escape($idx, 'Integer', FALSE) === NULL) {
668 return CRM_Core_Error
::createAPIError(ts('field ID needs to be of type Integer for index %1',
672 $fieldIDs[] = (int ) $idx;
676 $default = array('Contact', 'Individual', 'Household', 'Organization');
677 if (!($type = CRM_Utils_Array
::value('entityType', $params)) ||
678 in_array($params['entityType'], $default)
683 $entities = CRM_Core_SelectValues
::customGroupExtends();
684 if (!array_key_exists($type, $entities)) {
685 if (in_array($type, $entities)) {
686 $type = $entities[$type];
687 if (in_array($type, $default)) {
692 return CRM_Core_Error
::createAPIError(ts('Invalid entity type') . ': "' . $type . '"');
697 $values = self
::getEntityValues($params['entityID'],
701 if (empty($values)) {
702 // note that this behaviour is undesirable from an API point of view - it should return an empty array
703 // since this is also called by the merger code & not sure the consequences of changing
704 // are just handling undoing this in the api layer. ie. converting the error back into a success
707 'error_message' => 'No values found for the specified entity ID and custom field(s).',
714 'entityID' => $params['entityID'],
716 foreach ($values as $id => $value) {
717 $result["custom_{$id}"] = $value;