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