Merge pull request #4979 from xurizaemon/codingstandards-12
[civicrm-core.git] / CRM / Utils / Address / BatchUpdate.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
6a488035 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
6a488035
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 +--------------------------------------------------------------------+
26*/
27
28/**
29 * A PHP cron script to format all the addresses in the database. Currently
30 * it only does geocoding if the geocode values are not set. At a later
31 * stage we will also handle USPS address cleanup and other formatting
32 * issues
33 *
34 */
35class CRM_Utils_Address_BatchUpdate {
36
37 var $start = NULL;
38 var $end = NULL;
39 var $geocoding = 1;
40 var $parse = 1;
41 var $throttle = 0;
42
43 var $returnMessages = array();
44 var $returnError = 0;
45
5bc392e6 46 /**
c490a46a 47 * @param array $params
5bc392e6 48 */
6a488035
TO
49 public function __construct($params) {
50
51 foreach ($params as $name => $value) {
52 $this->$name = $value;
53 }
54
55 // fixme: more params verification
56 }
57
5bc392e6
EM
58 /**
59 * @return array
60 */
6a488035
TO
61 public function run() {
62
63 $config = &CRM_Core_Config::singleton();
64
65 // do check for geocoding.
66 $processGeocode = FALSE;
67 if (empty($config->geocodeMethod)) {
c8c9ce59 68 if (CRM_Utils_String::strtobool($this->geocoding) === TRUE) {
a08aa8e8 69 $this->returnMessages[] = ts('Error: You need to set a mapping provider under Administer > System Settings > Mapping and Geocoding');
6a488035
TO
70 $this->returnError = 1;
71 $this->returnResult();
72 }
73 }
74 else {
75 $processGeocode = TRUE;
76 // user might want to over-ride.
c8c9ce59 77 if (CRM_Utils_String::strtobool($this->geocoding) === FALSE) {
6a488035
TO
78 $processGeocode = FALSE;
79 }
80 }
81
82 // do check for parse street address.
83 $parseAddress = FALSE;
84 $parseAddress = CRM_Utils_Array::value('street_address_parsing',
85 CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
86 'address_options'
87 ),
88 FALSE
89 );
90 $parseStreetAddress = FALSE;
91 if (!$parseAddress) {
c8c9ce59 92 if (CRM_Utils_String::strtobool($this->parse) === TRUE) {
a08aa8e8 93 $this->returnMessages[] = ts('Error: You need to enable Street Address Parsing under Administer > Localization > Address Settings.');
6a488035
TO
94 $this->returnError = 1;
95 return $this->returnResult();
96 }
97 }
98 else {
99 $parseStreetAddress = TRUE;
100 // user might want to over-ride.
c8c9ce59 101 if (CRM_Utils_String::strtobool($this->parse) === FALSE) {
6a488035
TO
102 $parseStreetAddress = FALSE;
103 }
104 }
105
106 // don't process.
107 if (!$parseStreetAddress && !$processGeocode) {
108 $this->returnMessages[] = ts('Error: Both Geocode mapping as well as Street Address Parsing are disabled. You must configure one or both options to use this script.');
109 $this->returnError = 1;
110 return $this->returnResult();
111 }
112
113 // do check for parse street address.
114 return $this->processContacts($config, $processGeocode, $parseStreetAddress);
115 }
116
5bc392e6
EM
117 /**
118 * @param $config
119 * @param $processGeocode
120 * @param $parseStreetAddress
121 *
122 * @return array
123 * @throws Exception
124 */
00be9182 125 public function processContacts(&$config, $processGeocode, $parseStreetAddress) {
6a488035
TO
126 // build where clause.
127 $clause = array('( c.id = a.contact_id )');
18b8253b 128 $params = array();
6a488035 129 if ($this->start) {
18b8253b
DL
130 $clause[] = "( c.id >= %1 )";
131 $params[1] = array($this->start, 'Integer');
6a488035
TO
132 }
133
134 if ($this->end) {
18b8253b
DL
135 $clause[] = "( c.id <= %2 )";
136 $params[2] = array($this->end, 'Integer');
6a488035
TO
137 }
138
139 if ($processGeocode) {
140 $clause[] = '( a.geo_code_1 is null OR a.geo_code_1 = 0 )';
141 $clause[] = '( a.geo_code_2 is null OR a.geo_code_2 = 0 )';
142 $clause[] = '( a.country_id is not null )';
143 }
144
145 $whereClause = implode(' AND ', $clause);
146
147 $query = "
148 SELECT c.id,
149 a.id as address_id,
150 a.street_address,
151 a.city,
152 a.postal_code,
153 s.name as state,
154 o.name as country
155 FROM civicrm_contact c
156 INNER JOIN civicrm_address a ON a.contact_id = c.id
157 LEFT JOIN civicrm_country o ON a.country_id = o.id
158 LEFT JOIN civicrm_state_province s ON a.state_province_id = s.id
159 WHERE {$whereClause}
160 ORDER BY a.id
161 ";
162
163 $totalGeocoded = $totalAddresses = $totalAddressParsed = 0;
164
18b8253b 165 $dao = CRM_Core_DAO::executeQuery($query, $params);
6a488035 166 if ($processGeocode) {
e7292422 167 require_once str_replace('_', DIRECTORY_SEPARATOR, $config->geocodeMethod) . '.php';
6a488035
TO
168 }
169
6a488035
TO
170 $unparseableContactAddress = array();
171 while ($dao->fetch()) {
172 $totalAddresses++;
173 $params = array(
174 'street_address' => $dao->street_address,
175 'postal_code' => $dao->postal_code,
176 'city' => $dao->city,
177 'state_province' => $dao->state,
178 'country' => $dao->country,
179 );
180
181 $addressParams = array();
182
183 // process geocode.
184 if ($processGeocode) {
185 // loop through the address removing more information
186 // so we can get some geocode for a partial address
187 // i.e. city -> state -> country
188
189 $maxTries = 5;
190 do {
191 if ($this->throttle) {
192 usleep(5000000);
193 }
194
0e6e8724 195 $className = $config->geocodeMethod;
481a74f4 196 $className::format($params, TRUE);
79f1148d
DL
197
198 // see if we got a geocode error, in this case we'll trigger a fatal
199 // CRM-13760
200 if (
201 isset($params['geo_code_error']) &&
202 $params['geo_code_error'] == 'OVER_QUERY_LIMIT'
203 ) {
204 CRM_Core_Error::fatal('Aborting batch geocoding. Hit the over query limit on geocoder.');
205 }
206
6a488035
TO
207 array_shift($params);
208 $maxTries--;
79f1148d
DL
209 } while (
210 (!isset($params['geo_code_1']) || $params['geo_code_1'] == 'null') &&
6a488035
TO
211 ($maxTries > 1)
212 );
213
79f1148d 214 if (isset($params['geo_code_1']) && $params['geo_code_1'] != 'null') {
6a488035
TO
215 $totalGeocoded++;
216 $addressParams['geo_code_1'] = $params['geo_code_1'];
217 $addressParams['geo_code_2'] = $params['geo_code_2'];
d40fd297 218 $addressParams['postal_code'] = $params['postal_code'];
219 $addressParams['postal_code_suffix'] = $params['postal_code_suffix'];
6a488035
TO
220 }
221 }
222
223 // parse street address
224 if ($parseStreetAddress) {
225 $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($dao->street_address);
226 $success = TRUE;
227 // consider address is automatically parseable,
228 // when we should found street_number and street_name
8cc574cf 229 if (empty($parsedFields['street_name']) || empty($parsedFields['street_number'])) {
6a488035
TO
230 $success = FALSE;
231 }
232
233 // do check for all elements.
234 if ($success) {
235 $totalAddressParsed++;
236 }
237 elseif ($dao->street_address) {
238 //build contact edit url,
239 //so that user can manually fill the street address fields if the street address is not parsed, CRM-5886
240 $url = CRM_Utils_System::url('civicrm/contact/add', "reset=1&action=update&cid={$dao->id}");
241 $unparseableContactAddress[] = " Contact ID: " . $dao->id . " <a href =\"$url\"> " . $dao->street_address . " </a> ";
242 // reset element values.
243 $parsedFields = array_fill_keys(array_keys($parsedFields), '');
244 }
245 $addressParams = array_merge($addressParams, $parsedFields);
246 }
247
248 // finally update address object.
249 if (!empty($addressParams)) {
250 $address = new CRM_Core_DAO_Address();
251 $address->id = $dao->address_id;
252 $address->copyValues($addressParams);
253 $address->save();
254 $address->free();
255 }
256 }
257
258 $this->returnMessages[] = ts("Addresses Evaluated: %1", array(
389bcebf 259 1 => $totalAddresses,
353ffa53 260 )) . "\n";
6a488035
TO
261 if ($processGeocode) {
262 $this->returnMessages[] = ts("Addresses Geocoded: %1", array(
389bcebf 263 1 => $totalGeocoded,
353ffa53 264 )) . "\n";
6a488035
TO
265 }
266 if ($parseStreetAddress) {
267 $this->returnMessages[] = ts("Street Addresses Parsed: %1", array(
389bcebf 268 1 => $totalAddressParsed,
353ffa53 269 )) . "\n";
6a488035
TO
270 if ($unparseableContactAddress) {
271 $this->returnMessages[] = "<br />\n" . ts("Following is the list of contacts whose address is not parsed:") . "<br />\n";
272 foreach ($unparseableContactAddress as $contactLink) {
273 $this->returnMessages[] = $contactLink . "<br />\n";
274 }
275 }
276 }
277
278 return $this->returnResult();
279 }
280
5bc392e6
EM
281 /**
282 * @return array
283 */
00be9182 284 public function returnResult() {
353ffa53 285 $result = array();
6a488035
TO
286 $result['is_error'] = $this->returnError;
287 $result['messages'] = implode("", $this->returnMessages);
288 return $result;
289 }
290}