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