Ensure that we always return a raw urlencoded url for extenal urls to fix Flexmailer...
[civicrm-core.git] / CRM / Badge / BAO / Badge.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | This file is a part of CiviCRM. |
7 | |
8 | CiviCRM is free software; you can copy, modify, and distribute it |
9 | under the terms of the GNU Affero General Public License |
10 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
11 | |
12 | CiviCRM is distributed in the hope that it will be useful, but |
13 | WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
15 | See the GNU Affero General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU Affero General Public |
18 | License and the CiviCRM Licensing Exception along |
19 | with this program; if not, contact CiviCRM LLC |
20 | at info[AT]civicrm[DOT]org. If you have questions about the |
21 | GNU Affero General Public License or the licensing of CiviCRM, |
22 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
23 +--------------------------------------------------------------------+
24 */
25
26 /**
27 *
28 * @package CRM
29 * @copyright CiviCRM LLC https://civicrm.org/licensing
30 */
31
32 /**
33 * Class CRM_Badge_Format_Badge.
34 *
35 * parent class for building name badges
36 */
37 class CRM_Badge_BAO_Badge {
38
39 /**
40 * @var bool
41 */
42 public $debug = FALSE;
43
44 /**
45 * @var int
46 */
47 public $border = 0;
48
49 /**
50 * This function is called to create name label pdf.
51 *
52 * @param array $participants
53 * Associated array with participant info.
54 * @param array $layoutInfo
55 * Associated array which contains meta data about format/layout.
56 */
57 public function createLabels(&$participants, &$layoutInfo) {
58 $this->pdf = new CRM_Utils_PDF_Label($layoutInfo['format'], 'mm');
59 $this->pdf->Open();
60 $this->pdf->setPrintHeader(FALSE);
61 $this->pdf->setPrintFooter(FALSE);
62 $this->pdf->AddPage();
63 $this->pdf->SetGenerator($this, "generateLabel");
64
65 // this is very useful for debugging, by default set to FALSE
66 if ($this->debug) {
67 $this->border = "LTRB";
68 }
69
70 foreach ($participants as $participant) {
71 $formattedRow = self::formatLabel($participant, $layoutInfo);
72 $this->pdf->AddPdfLabel($formattedRow);
73 }
74
75 $this->pdf->Output(CRM_Utils_String::munge($layoutInfo['title'], '_', 64) . '.pdf', 'D');
76 CRM_Utils_System::civiExit();
77 }
78
79 /**
80 * Function to create structure and add meta data according to layout.
81 *
82 * @param array $row
83 * Row element that needs to be formatted.
84 * @param array $layout
85 * Layout meta data.
86 *
87 * @return array
88 * row with meta data
89 */
90 public static function formatLabel(&$row, &$layout) {
91 $formattedRow = ['labelFormat' => $layout['label_format_name']];
92 $formattedRow['labelTitle'] = $layout['title'];
93 $formattedRow['labelId'] = $layout['id'];
94
95 if (!empty($layout['data']['rowElements'])) {
96 foreach ($layout['data']['rowElements'] as $key => $element) {
97 $value = '';
98 if ($element) {
99 $value = $row[$element];
100 // hack to fix date field display format
101 if (strpos($element, '_date')) {
102 $value = CRM_Utils_Date::customFormat($value, "%B %E%f");
103 }
104 }
105
106 $formattedRow['token'][$key] = [
107 'value' => $value,
108 'font_name' => $layout['data']['font_name'][$key],
109 'font_size' => $layout['data']['font_size'][$key],
110 'font_style' => $layout['data']['font_style'][$key],
111 'text_alignment' => $layout['data']['text_alignment'][$key],
112 'token' => $layout['data']['token'][$key],
113 ];
114 }
115 }
116
117 if (!empty($layout['data']['image_1'])) {
118 $formattedRow['image_1'] = $layout['data']['image_1'];
119 }
120 if (!empty($layout['data']['width_image_1'])) {
121 $formattedRow['width_image_1'] = $layout['data']['width_image_1'];
122 }
123 if (!empty($layout['data']['height_image_1'])) {
124 $formattedRow['height_image_1'] = $layout['data']['height_image_1'];
125 }
126
127 if (!empty($layout['data']['image_2'])) {
128 $formattedRow['image_2'] = $layout['data']['image_2'];
129 }
130 if (!empty($layout['data']['width_image_2'])) {
131 $formattedRow['width_image_2'] = $layout['data']['width_image_2'];
132 }
133 if (!empty($layout['data']['height_image_2'])) {
134 $formattedRow['height_image_2'] = $layout['data']['height_image_2'];
135 }
136 if (!empty($row['image_URL']) && !empty($layout['data']['show_participant_image'])) {
137 $formattedRow['participant_image'] = $row['image_URL'];
138 }
139 if (!empty($layout['data']['width_participant_image'])) {
140 $formattedRow['width_participant_image'] = $layout['data']['width_participant_image'];
141 }
142 if (!empty($layout['data']['height_participant_image'])) {
143 $formattedRow['height_participant_image'] = $layout['data']['height_participant_image'];
144 }
145 if (!empty($layout['data']['alignment_participant_image'])) {
146 $formattedRow['alignment_participant_image'] = $layout['data']['alignment_participant_image'];
147 }
148
149 if (!empty($layout['data']['add_barcode'])) {
150 $formattedRow['barcode'] = [
151 'alignment' => $layout['data']['barcode_alignment'],
152 'type' => $layout['data']['barcode_type'],
153 ];
154 }
155
156 // finally assign all the row values, so that we can use it for barcode etc
157 $formattedRow['values'] = $row;
158
159 return $formattedRow;
160 }
161
162 /**
163 * @param $formattedRow
164 */
165 public function generateLabel($formattedRow) {
166 switch ($formattedRow['labelFormat']) {
167 case 'A6 Badge Portrait 150x106':
168 case 'Hanging Badge 3-3/4" x 4-3"/4':
169 self::labelCreator($formattedRow, 5);
170 break;
171
172 case 'Avery 5395':
173 default:
174 self::labelCreator($formattedRow);
175 break;
176 }
177 }
178
179 /**
180 * @param $formattedRow
181 * @param int $cellspacing
182 */
183 public function labelCreator(&$formattedRow, $cellspacing = 0) {
184 $this->lMarginLogo = 18;
185 $this->tMarginName = 20;
186
187 $x = $this->pdf->GetAbsX();
188 $y = $this->pdf->getY();
189
190 //call hook alterBadge
191 CRM_Utils_Hook::alterBadge($formattedRow['labelTitle'], $this, $formattedRow, $formattedRow['values']);
192
193 $startOffset = 0;
194 if (!empty($formattedRow['image_1'])) {
195 $this->printImage($formattedRow['image_1'], NULL, NULL, CRM_Utils_Array::value('width_image_1', $formattedRow),
196 CRM_Utils_Array::value('height_image_1', $formattedRow));
197 }
198
199 if (!empty($formattedRow['image_2'])) {
200 $this->printImage($formattedRow['image_2'], $x + 68, NULL, CRM_Utils_Array::value('width_image_2', $formattedRow),
201 CRM_Utils_Array::value('height_image_2', $formattedRow));
202 }
203
204 if ((CRM_Utils_Array::value('height_image_1', $formattedRow) >
205 CRM_Utils_Array::value('height_image_2', $formattedRow)) && !empty($formattedRow['height_image_1'])
206 ) {
207 $startOffset = CRM_Utils_Array::value('height_image_1', $formattedRow);
208 }
209 elseif (!empty($formattedRow['height_image_2'])) {
210 $startOffset = CRM_Utils_Array::value('height_image_2', $formattedRow);
211 }
212
213 if (!empty($formattedRow['participant_image'])) {
214 $imageAlign = 0;
215 switch (CRM_Utils_Array::value('alignment_participant_image', $formattedRow)) {
216 case 'R':
217 $imageAlign = 68;
218 break;
219
220 case 'L':
221 $imageAlign = 0;
222 break;
223
224 default:
225 break;
226 }
227 $this->pdf->Image($formattedRow['participant_image'], $x + $imageAlign, $y + $startOffset, CRM_Utils_Array::value('width_participant_image', $formattedRow), CRM_Utils_Array::value('height_participant_image', $formattedRow));
228 if ($startOffset == NULL && !empty($formattedRow['height_participant_image'])) {
229 $startOffset = $formattedRow['height_participant_image'];
230 }
231 }
232
233 $this->pdf->SetLineStyle([
234 'width' => 0.1,
235 'cap' => 'round',
236 'join' => 'round',
237 'dash' => '2,2',
238 'color' => [0, 0, 200],
239 ]);
240
241 $rowCount = CRM_Badge_Form_Layout::FIELD_ROWCOUNT;
242 for ($i = 1; $i <= $rowCount; $i++) {
243 if (!empty($formattedRow['token'][$i]['token'])) {
244 $value = '';
245 if ($formattedRow['token'][$i]['token'] != 'spacer') {
246 $value = $formattedRow['token'][$i]['value'];
247 }
248
249 $xAlign = $x;
250 $rowWidth = $this->pdf->width;
251 if (!empty($formattedRow['participant_image']) && !empty($formattedRow['width_participant_image'])) {
252 $rowWidth = $this->pdf->width - $formattedRow['width_participant_image'];
253 if ($formattedRow['alignment_participant_image'] == 'L') {
254 $xAlign = $x + $formattedRow['width_participant_image'] + $imageAlign;
255 }
256 }
257 $offset = $this->pdf->getY() + $startOffset + $cellspacing;
258
259 $this->pdf->SetFont($formattedRow['token'][$i]['font_name'], $formattedRow['token'][$i]['font_style'],
260 $formattedRow['token'][$i]['font_size']);
261 $this->pdf->MultiCell($rowWidth, 0, $value,
262 $this->border, $formattedRow['token'][$i]['text_alignment'], 0, 1, $xAlign, $offset);
263
264 // set this to zero so that it is added only for first element
265 $startOffset = 0;
266 }
267 }
268
269 if (!empty($formattedRow['barcode'])) {
270 $data = $formattedRow['values'];
271
272 if ($formattedRow['barcode']['type'] == 'barcode') {
273 $data['current_value'] = $formattedRow['values']['contact_id'] . '-' . $formattedRow['values']['participant_id'];
274 }
275 else {
276 // view participant url
277 $data['current_value'] = CRM_Utils_System::url('civicrm/contact/view/participant',
278 'action=view&reset=1&cid=' . $formattedRow['values']['contact_id'] . '&id='
279 . $formattedRow['values']['participant_id'],
280 TRUE,
281 NULL,
282 FALSE
283 );
284 }
285
286 // call hook alterBarcode
287 CRM_Utils_Hook::alterBarcode($data, $formattedRow['barcode']['type']);
288
289 if ($formattedRow['barcode']['type'] == 'barcode') {
290 // barcode position
291 $xAlign = $x;
292
293 switch ($formattedRow['barcode']['alignment']) {
294 case 'L':
295 $xAlign += -14;
296 break;
297
298 case 'R':
299 $xAlign += 27;
300 break;
301
302 case 'C':
303 $xAlign += 9;
304 break;
305 }
306
307 $style = [
308 'position' => '',
309 'align' => '',
310 'stretch' => FALSE,
311 'fitwidth' => TRUE,
312 'cellfitalign' => '',
313 'border' => FALSE,
314 'hpadding' => 13.5,
315 'vpadding' => 'auto',
316 'fgcolor' => [0, 0, 0],
317 'bgcolor' => FALSE,
318 'text' => FALSE,
319 'font' => 'helvetica',
320 'fontsize' => 8,
321 'stretchtext' => 0,
322 ];
323
324 $this->pdf->write1DBarcode($data['current_value'], 'C128', $xAlign, $y + $this->pdf->height - 10, '70',
325 12, 0.4, $style, 'B');
326 }
327 else {
328 // qr code position
329 $xAlign = $x;
330
331 switch ($formattedRow['barcode']['alignment']) {
332 case 'L':
333 $xAlign += -5;
334 break;
335
336 case 'R':
337 $xAlign += 56;
338 break;
339
340 case 'C':
341 $xAlign += 29;
342 break;
343 }
344
345 $style = [
346 'border' => FALSE,
347 'hpadding' => 13.5,
348 'vpadding' => 'auto',
349 'fgcolor' => [0, 0, 0],
350 'bgcolor' => FALSE,
351 'position' => '',
352 ];
353
354 $this->pdf->write2DBarcode($data['current_value'], 'QRCODE,H', $xAlign, $y + $this->pdf->height - 26, 30,
355 30, $style, 'B');
356 }
357 }
358 }
359
360 /**
361 * Helper function to print images.
362 *
363 * @param string $img
364 * Image url.
365 *
366 * @param string $x
367 * @param string $y
368 * @param null $w
369 * @param null $h
370 */
371 public function printImage($img, $x = '', $y = '', $w = NULL, $h = NULL) {
372 if (!$x) {
373 $x = $this->pdf->GetAbsX();
374 }
375
376 if (!$y) {
377 $y = $this->pdf->GetY();
378 }
379
380 $this->imgRes = 300;
381
382 if ($img) {
383 list($w, $h) = self::getImageProperties($img, $this->imgRes, $w, $h);
384 $this->pdf->Image($img, $x, $y, $w, $h, '', '', '', FALSE, 72, '', FALSE,
385 FALSE, $this->debug, FALSE, FALSE, FALSE);
386 }
387 $this->pdf->SetXY($x, $y);
388 }
389
390 /**
391 * @param $img
392 * @param int $imgRes
393 * @param null $w
394 * @param null $h
395 *
396 * @return array
397 */
398 public static function getImageProperties($img, $imgRes = 300, $w = NULL, $h = NULL) {
399 $imgsize = getimagesize($img);
400 $f = $imgRes / 25.4;
401 $w = !empty($w) ? $w : $imgsize[0] / $f;
402 $h = !empty($h) ? $h : $imgsize[1] / $f;
403 return [$w, $h];
404 }
405
406 /**
407 * Build badges parameters before actually creating badges.
408 *
409 * @param array $params
410 * Associated array of submitted values.
411 * @param CRM_Core_Form $form
412 */
413 public static function buildBadges(&$params, &$form) {
414 // get name badge layout info
415 $layoutInfo = CRM_Badge_BAO_Layout::buildLayout($params);
416
417 // split/get actual field names from token and individual contact image URLs
418 $returnProperties = [];
419 if (!empty($layoutInfo['data']['token'])) {
420 foreach ($layoutInfo['data']['token'] as $index => $value) {
421 $element = '';
422 if ($value) {
423 $token = CRM_Utils_Token::getTokens($value);
424 if (key($token) == 'contact') {
425 $element = $token['contact'][0];
426 }
427 elseif (key($token) == 'event') {
428 $element = $token['event'][0];
429 //FIX ME - we need to standardize event token names
430 if (substr($element, 0, 6) != 'event_') {
431 $element = 'event_' . $element;
432 }
433 }
434 elseif (key($token) == 'participant') {
435 $element = $token['participant'][0];
436 }
437
438 // build returnproperties for query
439 $returnProperties[$element] = 1;
440 }
441
442 // add actual field name to row element
443 $layoutInfo['data']['rowElements'][$index] = $element;
444 }
445 }
446
447 // add additional required fields for query execution
448 $additionalFields = ['participant_register_date', 'participant_id', 'event_id', 'contact_id', 'image_URL'];
449 foreach ($additionalFields as $field) {
450 $returnProperties[$field] = 1;
451 }
452
453 if ($form->_single) {
454 $queryParams = NULL;
455 }
456 else {
457 $queryParams = $form->get('queryParams');
458 }
459
460 $query = new CRM_Contact_BAO_Query($queryParams, $returnProperties, NULL, FALSE, FALSE,
461 CRM_Contact_BAO_Query::MODE_EVENT
462 );
463
464 list($select, $from, $where, $having) = $query->query();
465 if (empty($where)) {
466 $where = "WHERE {$form->_componentClause}";
467 }
468 else {
469 $where .= " AND {$form->_componentClause}";
470 }
471
472 $sortOrder = NULL;
473 if ($form->get(CRM_Utils_Sort::SORT_ORDER)) {
474 $sortOrder = $form->get(CRM_Utils_Sort::SORT_ORDER);
475 if (!empty($sortOrder)) {
476 $sortOrder = " ORDER BY $sortOrder";
477 }
478 }
479 $queryString = "$select $from $where $having $sortOrder";
480
481 $dao = CRM_Core_DAO::executeQuery($queryString);
482 $rows = [];
483 while ($dao->fetch()) {
484 $query->convertToPseudoNames($dao);
485 $rows[$dao->participant_id] = [];
486 foreach ($returnProperties as $key => $dontCare) {
487 $value = isset($dao->$key) ? $dao->$key : NULL;
488 // Format custom fields
489 if (strstr($key, 'custom_') && isset($value)) {
490 $value = CRM_Core_BAO_CustomField::displayValue($value, substr($key, 7), $dao->contact_id);
491 }
492 $rows[$dao->participant_id][$key] = $value;
493 }
494 }
495
496 $eventBadgeClass = new CRM_Badge_BAO_Badge();
497 $eventBadgeClass->createLabels($rows, $layoutInfo);
498 }
499
500 }