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