3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
19 * A PHP cron script to format all the addresses in the database. Currently
20 * it only does geocoding if the geocode values are not set. At a later
21 * stage we will also handle USPS address cleanup and other formatting
24 class CRM_Utils_Address_BatchUpdate
{
28 public $geocoding = 1;
32 public $returnMessages = [];
33 public $returnError = 0;
38 * @param array $params
40 public function __construct($params) {
42 foreach ($params as $name => $value) {
43 $this->$name = $value;
46 // fixme: more params verification
54 public function run() {
56 // do check for geocoding.
57 $processGeocode = FALSE;
58 if (!CRM_Utils_GeocodeProvider
::getUsableClassName()) {
59 if (CRM_Utils_String
::strtobool($this->geocoding
) === TRUE) {
60 $this->returnMessages
[] = ts('Error: You need to set a mapping provider under Administer > System Settings > Mapping and Geocoding');
61 $this->returnError
= 1;
62 $this->returnResult();
66 $processGeocode = TRUE;
67 // user might want to over-ride.
68 if (CRM_Utils_String
::strtobool($this->geocoding
) === FALSE) {
69 $processGeocode = FALSE;
73 // do check for parse street address.
74 $parseAddress = FALSE;
75 $parseAddress = CRM_Utils_Array
::value('street_address_parsing',
76 CRM_Core_BAO_Setting
::valueOptions(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
81 $parseStreetAddress = FALSE;
83 if (CRM_Utils_String
::strtobool($this->parse
) === TRUE) {
84 $this->returnMessages
[] = ts('Error: You need to enable Street Address Parsing under Administer > Localization > Address Settings.');
85 $this->returnError
= 1;
86 return $this->returnResult();
90 $parseStreetAddress = TRUE;
91 // user might want to over-ride.
92 if (CRM_Utils_String
::strtobool($this->parse
) === FALSE) {
93 $parseStreetAddress = FALSE;
98 if (!$parseStreetAddress && !$processGeocode) {
99 $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.');
100 $this->returnError
= 1;
101 return $this->returnResult();
104 // do check for parse street address.
105 return $this->processContacts($processGeocode, $parseStreetAddress);
111 * @param bool $processGeocode
112 * @param bool $parseStreetAddress
117 public function processContacts($processGeocode, $parseStreetAddress) {
118 // build where clause.
119 $clause = ['( c.id = a.contact_id )'];
122 $clause[] = "( c.id >= %1 )";
123 $params[1] = [$this->start
, 'Integer'];
127 $clause[] = "( c.id <= %2 )";
128 $params[2] = [$this->end
, 'Integer'];
131 if ($processGeocode) {
132 $clause[] = '( a.geo_code_1 is null OR a.geo_code_1 = 0 )';
133 $clause[] = '( a.geo_code_2 is null OR a.geo_code_2 = 0 )';
134 $clause[] = '( a.country_id is not null )';
137 $whereClause = implode(' AND ', $clause);
148 FROM civicrm_contact c
149 INNER JOIN civicrm_address a ON a.contact_id = c.id
150 LEFT JOIN civicrm_country o ON a.country_id = o.id
151 LEFT JOIN civicrm_state_province s ON a.state_province_id = s.id
156 $totalGeocoded = $totalAddresses = $totalAddressParsed = 0;
158 $dao = CRM_Core_DAO
::executeQuery($query, $params);
160 $unparseableContactAddress = [];
161 while ($dao->fetch()) {
164 'street_address' => $dao->street_address
,
165 'postal_code' => $dao->postal_code
,
166 'city' => $dao->city
,
167 'state_province' => $dao->state
,
168 'country' => $dao->country
,
169 'country_id' => $dao->country_id
,
175 if ($processGeocode) {
176 // loop through the address removing more information
177 // so we can get some geocode for a partial address
178 // i.e. city -> state -> country
182 if ($this->throttle
) {
186 CRM_Core_BAO_Address
::addGeocoderData($params);
188 // see if we got a geocode error, in this case we'll trigger a fatal
191 isset($params['geo_code_error']) &&
192 $params['geo_code_error'] == 'OVER_QUERY_LIMIT'
194 throw new CRM_Core_Exception('Aborting batch geocoding. Hit the over query limit on geocoder.');
197 array_shift($params);
200 (!isset($params['geo_code_1']) ||
$params['geo_code_1'] == 'null') &&
204 if (isset($params['geo_code_1']) && $params['geo_code_1'] != 'null') {
206 $addressParams = $params;
210 // parse street address
211 if ($parseStreetAddress) {
212 $parsedFields = CRM_Core_BAO_Address
::parseStreetAddress($dao->street_address
);
214 // consider address is automatically parseable,
215 // when we should found street_number and street_name
216 if (empty($parsedFields['street_name']) ||
empty($parsedFields['street_number'])) {
220 // do check for all elements.
222 $totalAddressParsed++
;
224 elseif ($dao->street_address
) {
225 //build contact edit url,
226 //so that user can manually fill the street address fields if the street address is not parsed, CRM-5886
227 $url = CRM_Utils_System
::url('civicrm/contact/add', "reset=1&action=update&cid={$dao->id}");
228 $unparseableContactAddress[] = " Contact ID: " . $dao->id
. " <a href =\"$url\"> " . $dao->street_address
. " </a> ";
229 // reset element values.
230 $parsedFields = array_fill_keys(array_keys($parsedFields), '');
232 $addressParams = array_merge($addressParams, $parsedFields);
235 // finally update address object.
236 if (!empty($addressParams)) {
237 $address = new CRM_Core_DAO_Address();
238 $address->id
= $dao->address_id
;
239 $address->copyValues($addressParams);
244 $this->returnMessages
[] = ts("Addresses Evaluated: %1", [
245 1 => $totalAddresses,
247 if ($processGeocode) {
248 $this->returnMessages
[] = ts("Addresses Geocoded: %1", [
252 if ($parseStreetAddress) {
253 $this->returnMessages
[] = ts("Street Addresses Parsed: %1", [
254 1 => $totalAddressParsed,
256 if ($unparseableContactAddress) {
257 $this->returnMessages
[] = "<br />\n" . ts("Following is the list of contacts whose address is not parsed:") . "<br />\n";
258 foreach ($unparseableContactAddress as $contactLink) {
259 $this->returnMessages
[] = $contactLink . "<br />\n";
264 return $this->returnResult();
272 public function returnResult() {
274 $result['is_error'] = $this->returnError
;
275 $result['messages'] = '';
276 // Pad message size to allow for prefix added by CRM_Core_JobManager.
278 // Ensure that each message can fit in the civicrm_job_log.data column.
279 foreach ($this->returnMessages
as $message) {
280 $messageSize +
= strlen($message);
281 if ($messageSize > CRM_Utils_Type
::BLOB_SIZE
) {
282 $result['messages'] .= '...';
285 $result['messages'] .= $message;