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