Merge pull request #4689 from colemanw/CRM-13611
[civicrm-core.git] / CRM / Contact / Form / Task / Label.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2014
32 * $Id$
33 *
34 */
35
36 /**
37 * This class helps to print the labels for contacts
38 *
39 */
40 class CRM_Contact_Form_Task_Label extends CRM_Contact_Form_Task {
41
42 /**
43 * Build all the data structures needed to build the form
44 *
45 * @return void
46 * @access public
47 */
48 function preProcess() {
49 $this->set('contactIds', $this->_contactIds);
50 parent::preProcess();
51 }
52
53 /**
54 * Build the form object
55 *
56 * @access public
57 *
58 * @return void
59 */
60 function buildQuickForm() {
61 CRM_Utils_System::setTitle(ts('Make Mailing Labels'));
62
63 //add select for label
64 $label = CRM_Core_BAO_LabelFormat::getList(TRUE);
65
66 $this->add('select', 'label_name', ts('Select Label'), array('' => ts('- select label -')) + $label, TRUE);
67
68
69 // add select for Location Type
70 $this->addElement('select', 'location_type_id', ts('Select Location'),
71 array(
72 '' => ts('Primary')) + CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'), TRUE
73 );
74
75 // checkbox for SKIP contacts with Do Not Mail privacy option
76 $this->addElement('checkbox', 'do_not_mail', ts('Do not print labels for contacts with "Do Not Mail" privacy option checked'));
77
78 $this->add('checkbox', 'merge_same_address', ts('Merge labels for contacts with the same address'), NULL);
79 $this->add('checkbox', 'merge_same_household', ts('Merge labels for contacts belonging to the same household'), NULL);
80
81 $this->addButtons(array(
82 array(
83 'type' => 'submit',
84 'name' => ts('Make Mailing Labels'),
85 'isDefault' => TRUE,
86 ),
87 array(
88 'type' => 'cancel',
89 'name' => ts('Done'),
90 ),
91 ));
92 }
93
94 /**
95 * Set default values for the form.
96 *
97 * @param null
98 *
99 * @return array array of default values
100 * @access public
101 */
102 function setDefaultValues() {
103 $defaults = array();
104 $format = CRM_Core_BAO_LabelFormat::getDefaultValues();
105 $defaults['label_name'] = CRM_Utils_Array::value('name', $format);
106 $defaults['do_not_mail'] = 1;
107
108 return $defaults;
109 }
110
111 /**
112 * Process the form after the input has been submitted and validated
113 *
114 * @access public
115 *
116 * @return void
117 */
118 public function postProcess() {
119 $fv = $this->controller->exportValues($this->_name);
120 $config = CRM_Core_Config::singleton();
121 $locName = NULL;
122 //get the address format sequence from the config file
123 $mailingFormat = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
124 'mailing_format'
125 );
126
127 $sequence = CRM_Utils_Address::sequence($mailingFormat);
128
129 foreach ($sequence as $v) {
130 $address[$v] = 1;
131 }
132
133 if (array_key_exists('postal_code', $address)) {
134 $address['postal_code_suffix'] = 1;
135 }
136
137 //build the returnproperties
138 $returnProperties = array('display_name' => 1, 'contact_type' => 1, 'prefix_id' => 1);
139 $mailingFormat = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
140 'mailing_format'
141 );
142
143 $mailingFormatProperties = array();
144 if ($mailingFormat) {
145 $mailingFormatProperties = self::getReturnProperties($mailingFormat);
146 $returnProperties = array_merge($returnProperties, $mailingFormatProperties);
147 }
148 //we should not consider addressee for data exists, CRM-6025
149 if (array_key_exists('addressee', $mailingFormatProperties)) {
150 unset($mailingFormatProperties['addressee']);
151 }
152
153 $customFormatProperties = array();
154 if (stristr($mailingFormat, 'custom_')) {
155 foreach ($mailingFormatProperties as $token => $true) {
156 if (substr($token, 0, 7) == 'custom_') {
157 if (empty($customFormatProperties[$token])) {
158 $customFormatProperties[$token] = $mailingFormatProperties[$token];
159 }
160 }
161 }
162 }
163
164 if (!empty($customFormatProperties)) {
165 $returnProperties = array_merge($returnProperties, $customFormatProperties);
166 }
167
168 if (isset($fv['merge_same_address'])) {
169 // we need first name/last name for summarising to avoid spillage
170 $returnProperties['first_name'] = 1;
171 $returnProperties['last_name'] = 1;
172 }
173
174 $individualFormat = FALSE;
175
176 /*
177 * CRM-8338: replace ids of household members with the id of their household
178 * so we can merge labels by household.
179 */
180 if (isset($fv['merge_same_household'])) {
181 $this->mergeContactIdsByHousehold();
182 $individualFormat = TRUE;
183 }
184
185 //get the contacts information
186 $params = array();
187 if (!empty($fv['location_type_id'])) {
188 $locType = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
189 $locName = $locType[$fv['location_type_id']];
190 $location = array('location' => array("{$locName}" => $address));
191 $returnProperties = array_merge($returnProperties, $location);
192 $params[] = array('location_type', '=', array($fv['location_type_id'] => 1), 0, 0);
193 }
194 else {
195 $returnProperties = array_merge($returnProperties, $address);
196 }
197
198 $rows = array();
199
200 foreach ($this->_contactIds as $key => $contactID) {
201 $params[] = array(
202 CRM_Core_Form::CB_PREFIX . $contactID,
203 '=', 1, 0, 0,
204 );
205 }
206
207 // fix for CRM-2651
208 if (!empty($fv['do_not_mail'])) {
209 $params[] = array('do_not_mail', '=', 0, 0, 0);
210 }
211 // fix for CRM-2613
212 $params[] = array('is_deceased', '=', 0, 0, 0);
213
214 $custom = array();
215 foreach ($returnProperties as $name => $dontCare) {
216 $cfID = CRM_Core_BAO_CustomField::getKeyID($name);
217 if ($cfID) {
218 $custom[] = $cfID;
219 }
220 }
221
222 //get the total number of contacts to fetch from database.
223 $numberofContacts = count($this->_contactIds);
224 $query = new CRM_Contact_BAO_Query($params, $returnProperties);
225 $details = $query->apiQuery($params, $returnProperties, NULL, NULL, 0, $numberofContacts);
226
227 $messageToken = CRM_Utils_Token::getTokens($mailingFormat);
228
229 // also get all token values
230 CRM_Utils_Hook::tokenValues($details[0],
231 $this->_contactIds,
232 NULL,
233 $messageToken,
234 'CRM_Contact_Form_Task_Label'
235 );
236
237 $tokens = array();
238 CRM_Utils_Hook::tokens($tokens);
239 $tokenFields = array();
240 foreach ($tokens as $category => $catTokens) {
241 foreach ($catTokens as $token => $tokenName) {
242 $tokenFields[] = $token;
243 }
244 }
245
246 foreach ($this->_contactIds as $value) {
247 foreach ($custom as $cfID) {
248 if (isset($details[0][$value]["custom_{$cfID}"])) {
249 $details[0][$value]["custom_{$cfID}"] = CRM_Core_BAO_CustomField::getDisplayValue($details[0][$value]["custom_{$cfID}"], $cfID, $details[1]);
250 }
251 }
252 $contact = CRM_Utils_Array::value($value, $details['0']);
253
254 if (is_a($contact, 'CRM_Core_Error')) {
255 return NULL;
256 }
257
258 // we need to remove all the "_id"
259 unset($contact['contact_id']);
260
261 if ($locName && !empty($contact[$locName])) {
262 // If location type is not primary, $contact contains
263 // one more array as "$contact[$locName] = array( values... )"
264
265 if(!$this->tokenIsFound($contact, $mailingFormatProperties, $tokenFields)) {
266 continue;
267 }
268
269 $contact = array_merge($contact, $contact[$locName]);
270 unset($contact[$locName]);
271
272 if (!empty($contact['county_id'])) {
273 unset($contact['county_id']);
274 }
275
276 foreach ($contact as $field => $fieldValue) {
277 $rows[$value][$field] = $fieldValue;
278 }
279
280 $valuesothers = array();
281 $paramsothers = array('contact_id' => $value);
282 $valuesothers = CRM_Core_BAO_Location::getValues($paramsothers, $valuesothers);
283 if (!empty($fv['location_type_id'])) {
284 foreach ($valuesothers as $vals) {
285 if ( CRM_Utils_Array::value('location_type_id', $vals) ==
286 CRM_Utils_Array::value('location_type_id', $fv ) ) {
287 foreach ($vals as $k => $v) {
288 if (in_array($k, array(
289 'email', 'phone', 'im', 'openid'))) {
290 if ($k == 'im') {
291 $rows[$value][$k] = $v['1']['name'];
292 }
293 else {
294 $rows[$value][$k] = $v['1'][$k];
295 }
296 $rows[$value][$k . '_id'] = $v['1']['id'];
297 }
298 }
299 }
300 }
301 }
302 }
303 else {
304 if(!$this->tokenIsFound($contact, $mailingFormatProperties, $tokenFields)) {
305 continue;
306 }
307
308 if (!empty($contact['addressee_display'])) {
309 $contact['addressee_display'] = trim($contact['addressee_display']);
310 }
311 if (!empty($contact['addressee'])) {
312 $contact['addressee'] = $contact['addressee_display'];
313 }
314
315 // now create the rows for generating mailing labels
316 foreach ($contact as $field => $fieldValue) {
317 $rows[$value][$field] = $fieldValue;
318 }
319 }
320 }
321
322 if (isset($fv['merge_same_address'])) {
323 $this->mergeSameAddress($rows);
324 $individualFormat = TRUE;
325 }
326
327 // format the addresses according to CIVICRM_ADDRESS_FORMAT (CRM-1327)
328 foreach ($rows as $id => $row) {
329 if ($commMethods = CRM_Utils_Array::value('preferred_communication_method', $row)) {
330 $val = array_filter(explode(CRM_Core_DAO::VALUE_SEPARATOR, $commMethods));
331 $comm = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'preferred_communication_method');
332 $temp = array();
333 foreach ($val as $vals) {
334 $temp[] = $comm[$vals];
335 }
336 $row['preferred_communication_method'] = implode(', ', $temp);
337 }
338 $row['id'] = $id;
339 $formatted = CRM_Utils_Address::format($row, 'mailing_format', FALSE, TRUE, $individualFormat, $tokenFields);
340
341 // CRM-2211: UFPDF doesn't have bidi support; use the PECL fribidi package to fix it.
342 // On Ubuntu (possibly Debian?) be aware of http://pecl.php.net/bugs/bug.php?id=12366
343 // Due to FriBidi peculiarities, this can't be called on
344 // a multi-line string, hence the explode+implode approach.
345 if (function_exists('fribidi_log2vis')) {
346 $lines = explode("\n", $formatted);
347 foreach ($lines as $i => $line) {
348 $lines[$i] = fribidi_log2vis($line, FRIBIDI_AUTO, FRIBIDI_CHARSET_UTF8);
349 }
350 $formatted = implode("\n", $lines);
351 }
352 $rows[$id] = array($formatted);
353 }
354
355 //call function to create labels
356 self::createLabel($rows, $fv['label_name']);
357 CRM_Utils_System::civiExit(1);
358 }
359
360 /**
361 * Check for presence of tokens to be swapped out
362 *
363 * @param array $contact
364 * @param array $mailingFormatProperties
365 * @param array $tokenFields
366 *
367 * @return bool
368 */
369 function tokenIsFound($contact, $mailingFormatProperties, $tokenFields) {
370 foreach (array_merge($mailingFormatProperties, array_fill_keys($tokenFields, 1)) as $key => $dontCare) {
371 if (!empty($contact[$key])) {
372 return TRUE;
373 }
374 }
375 return FALSE;
376 }
377 /**
378 * Create labels (pdf)
379 *
380 * @param array $contactRows assciated array of contact data
381 * @param string $format format in which labels needs to be printed
382 * @param string $fileName The name of the file to save the label in
383 *
384 * @return null
385 * @access public
386 */
387 function createLabel(&$contactRows, &$format, $fileName = 'MailingLabels_CiviCRM.pdf') {
388 $pdf = new CRM_Utils_PDF_Label($format, 'mm');
389 $pdf->Open();
390 $pdf->AddPage();
391
392 //build contact string that needs to be printed
393 $val = NULL;
394 foreach ($contactRows as $row => $value) {
395 foreach ($value as $k => $v) {
396 $val .= "$v\n";
397 }
398
399 $pdf->AddPdfLabel($val);
400 $val = '';
401 }
402 $pdf->Output($fileName, 'D');
403 }
404
405 /**
406 * Create the array of returnProperties
407 *
408 * @param string $format format for which return properties build
409 *
410 * @return array of returnProperties
411 * @access public
412 */
413 function getReturnProperties(&$format) {
414 $returnProperties = array();
415 $matches = array();
416 preg_match_all('/(?<!\{|\\\\)\{(\w+\.\w+)\}(?!\})/',
417 $format,
418 $matches,
419 PREG_PATTERN_ORDER
420 );
421 if ($matches[1]) {
422 foreach ($matches[1] as $token) {
423 list($type, $name) = preg_split('/\./', $token, 2);
424 if ($name) {
425 $returnProperties["{$name}"] = 1;
426 }
427 }
428 }
429
430 return $returnProperties;
431 }
432
433 /**
434 * @param $rows
435 */
436 function mergeSameAddress(&$rows) {
437 $uniqueAddress = array();
438 foreach (array_keys($rows) as $rowID) {
439 // load complete address as array key
440 $address =
441 trim($rows[$rowID]['street_address']) . trim($rows[$rowID]['city']) . trim($rows[$rowID]['state_province']) . trim($rows[$rowID]['postal_code']) . trim($rows[$rowID]['country']);
442 if (isset($rows[$rowID]['last_name'])) {
443 $name = $rows[$rowID]['last_name'];
444 }
445 else {
446 $name = $rows[$rowID]['display_name'];
447 }
448
449 // CRM-15120
450 $formatted = array(
451 'first_name' => $rows[$rowID]['first_name'],
452 'individual_prefix' => $rows[$rowID]['individual_prefix']
453 );
454 $format = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'display_name_format');
455 $firstNameWithPrefix = CRM_Utils_Address::format($formatted, $format, FALSE, FALSE, TRUE);
456 $firstNameWithPrefix = trim($firstNameWithPrefix);
457
458 // fill uniqueAddress array with last/first name tree
459 if (isset($uniqueAddress[$address])) {
460 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['first_name'] = $rows[$rowID]['first_name'];
461 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['addressee_display'] = $rows[$rowID]['addressee_display'];
462 // drop unnecessary rows
463 unset($rows[$rowID]);
464 // this is the first listing at this address
465 }
466 else {
467 $uniqueAddress[$address]['ID'] = $rowID;
468 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['first_name'] = $rows[$rowID]['first_name'];
469 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['addressee_display'] = $rows[$rowID]['addressee_display'];
470 }
471 }
472 foreach ($uniqueAddress as $address => $data) {
473 // copy data back to $rows
474 $count = 0;
475 // one last name list per row
476 foreach ($data['names'] as $last_name => $first_names) {
477 // too many to list
478 if ($count > 2) {
479 break;
480 }
481 if(count($first_names) == 1){
482 $family = $first_names[current(array_keys($first_names))]['addressee_display'];
483 }
484 else {
485 // collapse the tree to summarize
486 $family = trim(implode(" & ", array_keys($first_names)) . " " . $last_name);
487 }
488 if ($count) {
489 $processedNames .= "\n" . $family;
490 }
491 else {
492 // build display_name string
493 $processedNames = $family;
494 }
495 $count++;
496 }
497 $rows[$data['ID']]['addressee'] = $rows[$data['ID']]['addressee_display'] = $rows[$data['ID']]['display_name'] = $processedNames;
498 }
499 }
500 }
501