(NFC) (dev/core#878) Simplify copyright header (CRM/*)
[civicrm-core.git] / CRM / Utils / API / MatchOption.php
CommitLineData
c8463688
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
c8463688 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 |
c8463688 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
c8463688
TO
11
12/**
13 * Implement the "match" and "match-mandatory" options. If the submitted record doesn't have an ID
14 * but a "match" key is specified, then we will automatically search for pre-existing record and
e4b4e33a
TO
15 * fill-in the missing ID. The "match" or "match-mandatory" can specified as a string (the name of the key
16 * to match on) or array (the names of several keys to match on).
c8463688
TO
17 *
18 * Note that "match" and "match-mandatory" behave the same in the case where one matching record
19 * exists (ie they update the record). They also behave the same if there are multiple matching
20 * records (ie they throw an error). However, if there is no matching record, they differ:
21 * - "match-mandatory" will generate an error
22 * - "match" will allow action to proceed -- thus inserting a new record
23 *
24 * @code
25 * $result = civicrm_api('contact', 'create', array(
26 * 'options' => array(
27 * 'match' => array('last_name', 'first_name')
28 * ),
29 * 'first_name' => 'Jeffrey',
30 * 'last_name' => 'Lebowski',
31 * 'nick_name' => 'The Dude',
32 * ));
33 * @endcode
34 *
35 * @package CRM
ca5cec67 36 * @copyright CiviCRM LLC https://civicrm.org/licensing
c8463688
TO
37 */
38
39require_once 'api/Wrapper.php';
5bc392e6
EM
40
41/**
42 * Class CRM_Utils_API_MatchOption
43 */
c8463688
TO
44class CRM_Utils_API_MatchOption implements API_Wrapper {
45
46 /**
47 * @var CRM_Utils_API_MatchOption
48 */
49 private static $_singleton = NULL;
50
51 /**
3d469574 52 * Singleton function.
53 *
c8463688
TO
54 * @return CRM_Utils_API_MatchOption
55 */
56 public static function singleton() {
57 if (self::$_singleton === NULL) {
58 self::$_singleton = new CRM_Utils_API_MatchOption();
59 }
60 return self::$_singleton;
61 }
62
63 /**
da2e61bf 64 * @inheritDoc
c8463688
TO
65 */
66 public function fromApiInput($apiRequest) {
7ebee129 67
e4b4e33a 68 // Parse options.match or options.match-mandatory
e8e8f3ad 69 $keys = NULL;
7ebee129 70 if (isset($apiRequest['params'], $apiRequest['params']['options']) && is_array($apiRequest['params']['options'])) {
c8463688
TO
71 if (isset($apiRequest['params']['options']['match-mandatory'])) {
72 $isMandatory = TRUE;
73 $keys = $apiRequest['params']['options']['match-mandatory'];
74 }
e4b4e33a 75 elseif (isset($apiRequest['params']['options']['match'])) {
c8463688
TO
76 $isMandatory = FALSE;
77 $keys = $apiRequest['params']['options']['match'];
78 }
e4b4e33a 79 if (is_string($keys)) {
be2fb01f 80 $keys = [$keys];
e4b4e33a
TO
81 }
82 }
c8463688 83
e4b4e33a
TO
84 // If one of the options was specified, then try to match records.
85 // Matching logic differs for 'create' and 'replace' actions.
86 if ($keys !== NULL) {
22e263ad 87 switch ($apiRequest['action']) {
e4b4e33a
TO
88 case 'create':
89 if (empty($apiRequest['params']['id'])) {
90 $apiRequest['params'] = $this->match($apiRequest['entity'], $apiRequest['params'], $keys, $isMandatory);
91 }
92 break;
e7292422 93
e4b4e33a
TO
94 case 'replace':
95 // In addition to matching on the listed keys, also match on the set-definition keys.
96 // For example, if the $apiRequest is to "replace the set of civicrm_emails for contact_id=123 while
97 // matching emails on location_type_id", then we would need to search for pre-existing emails using
98 // both 'contact_id' and 'location_type_id'
99 $baseParams = _civicrm_api3_generic_replace_base_params($apiRequest['params']);
100 $keys = array_unique(array_merge(
101 array_keys($baseParams),
102 $keys
103 ));
104
105 // attempt to match each replacement item
22e263ad 106 foreach ($apiRequest['params']['values'] as $offset => $createParams) {
e4b4e33a
TO
107 $createParams = array_merge($baseParams, $createParams);
108 $createParams = $this->match($apiRequest['entity'], $createParams, $keys, $isMandatory);
109 $apiRequest['params']['values'][$offset] = $createParams;
110 }
111 break;
e7292422 112
e4b4e33a 113 default:
50bfb460 114 // be forgiving of sloppy api calls
c8463688
TO
115 }
116 }
e4b4e33a 117
c8463688
TO
118 return $apiRequest;
119 }
120
e4b4e33a
TO
121 /**
122 * Attempt to match a contact. This filters/updates the $createParams if there is a match.
123 *
124 * @param string $entity
125 * @param array $createParams
126 * @param array $keys
127 * @param bool $isMandatory
e8e8f3ad 128 *
a6c01b45
CW
129 * @return array
130 * revised $createParams, including 'id' if known
e4b4e33a
TO
131 * @throws API_Exception
132 */
133 public function match($entity, $createParams, $keys, $isMandatory) {
134 $getParams = $this->createGetParams($createParams, $keys);
135 $getResult = civicrm_api3($entity, 'get', $getParams);
136 if ($getResult['count'] == 0) {
137 if ($isMandatory) {
138 throw new API_Exception("Failed to match existing record");
139 }
6714d8d2
SL
140 // OK, don't care
141 return $createParams;
e4b4e33a
TO
142 }
143 elseif ($getResult['count'] == 1) {
144 $item = array_shift($getResult['values']);
145 $createParams['id'] = $item['id'];
146 return $createParams;
147 }
148 else {
149 throw new API_Exception("Ambiguous match criteria");
150 }
151 }
152
c8463688 153 /**
da2e61bf 154 * @inheritDoc
c8463688
TO
155 */
156 public function toApiOutput($apiRequest, $result) {
157 return $result;
158 }
159
160 /**
161 * Create APIv3 "get" parameters to lookup an existing record using $keys
162 *
77855840
TO
163 * @param array $origParams
164 * Api request.
165 * @param array $keys
166 * List of keys to match against.
77b97be7 167 *
a6c01b45
CW
168 * @return array
169 * APIv3 $params
c8463688 170 */
00be9182 171 public function createGetParams($origParams, $keys) {
be2fb01f 172 $params = ['version' => 3];
c8463688 173 foreach ($keys as $key) {
e4b4e33a 174 $params[$key] = CRM_Utils_Array::value($key, $origParams, '');
c8463688
TO
175 }
176 return $params;
177 }
96025800 178
c8463688 179}