Merge pull request #21469 from mattwire/updatesubscriptionform
[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 // hack to fix date field display format
92 if (in_array($element, ['event_start_date', 'event_end_date'], TRUE)) {
93 $value = CRM_Utils_Date::customFormat($value, "%B %E%f");
94 }
95 }
96
97 $formattedRow['token'][$key] = [
98 'value' => $value,
99 'font_name' => $layout['data']['font_name'][$key],
100 'font_size' => $layout['data']['font_size'][$key],
101 'font_style' => $layout['data']['font_style'][$key],
102 'text_alignment' => $layout['data']['text_alignment'][$key],
103 'token' => $layout['data']['token'][$key],
104 ];
105 }
106 }
107
108 if (!empty($layout['data']['image_1'])) {
109 $formattedRow['image_1'] = $layout['data']['image_1'];
110 }
111 if (!empty($layout['data']['width_image_1'])) {
112 $formattedRow['width_image_1'] = $layout['data']['width_image_1'];
113 }
114 if (!empty($layout['data']['height_image_1'])) {
115 $formattedRow['height_image_1'] = $layout['data']['height_image_1'];
116 }
117
118 if (!empty($layout['data']['image_2'])) {
119 $formattedRow['image_2'] = $layout['data']['image_2'];
120 }
121 if (!empty($layout['data']['width_image_2'])) {
122 $formattedRow['width_image_2'] = $layout['data']['width_image_2'];
123 }
124 if (!empty($layout['data']['height_image_2'])) {
125 $formattedRow['height_image_2'] = $layout['data']['height_image_2'];
126 }
127 if (!empty($row['image_URL']) && !empty($layout['data']['show_participant_image'])) {
128 $formattedRow['participant_image'] = $row['image_URL'];
129 }
130 if (!empty($layout['data']['width_participant_image'])) {
131 $formattedRow['width_participant_image'] = $layout['data']['width_participant_image'];
132 }
133 if (!empty($layout['data']['height_participant_image'])) {
134 $formattedRow['height_participant_image'] = $layout['data']['height_participant_image'];
135 }
136 if (!empty($layout['data']['alignment_participant_image'])) {
137 $formattedRow['alignment_participant_image'] = $layout['data']['alignment_participant_image'];
138 }
139
140 if (!empty($layout['data']['add_barcode'])) {
141 $formattedRow['barcode'] = [
142 'alignment' => $layout['data']['barcode_alignment'],
143 'type' => $layout['data']['barcode_type'],
144 ];
145 }
146
147 // finally assign all the row values, so that we can use it for barcode etc
148 $formattedRow['values'] = $row;
149
150 return $formattedRow;
151 }
152
153 /**
154 * @param $formattedRow
155 */
156 public function generateLabel($formattedRow) {
157 switch ($formattedRow['labelFormat']) {
158 case 'A6 Badge Portrait 150x106':
159 case 'Hanging Badge 3-3/4" x 4-3"/4':
160 self::labelCreator($formattedRow, 5);
161 break;
162
163 case 'Avery 5395':
164 default:
165 self::labelCreator($formattedRow);
166 break;
167 }
168 }
169
170 /**
171 * @param $formattedRow
172 * @param int $cellspacing
173 */
174 public function labelCreator(&$formattedRow, $cellspacing = 0) {
175 $this->lMarginLogo = 18;
176 $this->tMarginName = 20;
177
178 $x = $this->pdf->GetAbsX();
179 $y = $this->pdf->getY();
180
181 //call hook alterBadge
182 CRM_Utils_Hook::alterBadge($formattedRow['labelTitle'], $this, $formattedRow, $formattedRow['values']);
183
184 $startOffset = 0;
185 if (!empty($formattedRow['image_1'])) {
186 $this->printImage($formattedRow['image_1'], NULL, NULL, CRM_Utils_Array::value('width_image_1', $formattedRow),
187 CRM_Utils_Array::value('height_image_1', $formattedRow));
188 }
189
190 if (!empty($formattedRow['image_2'])) {
191 $this->printImage($formattedRow['image_2'], $x + 68, NULL, CRM_Utils_Array::value('width_image_2', $formattedRow),
192 CRM_Utils_Array::value('height_image_2', $formattedRow));
193 }
194
195 if ((CRM_Utils_Array::value('height_image_1', $formattedRow) >
196 CRM_Utils_Array::value('height_image_2', $formattedRow)) && !empty($formattedRow['height_image_1'])
197 ) {
198 $startOffset = $formattedRow['height_image_1'] ?? NULL;
199 }
200 elseif (!empty($formattedRow['height_image_2'])) {
201 $startOffset = $formattedRow['height_image_2'] ?? NULL;
202 }
203
204 if (!empty($formattedRow['participant_image'])) {
205 $imageAlign = 0;
206 switch (CRM_Utils_Array::value('alignment_participant_image', $formattedRow)) {
207 case 'R':
208 $imageAlign = 68;
209 break;
210
211 case 'L':
212 $imageAlign = 0;
213 break;
214
215 default:
216 break;
217 }
218 $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));
219 if ($startOffset == NULL && !empty($formattedRow['height_participant_image'])) {
220 $startOffset = $formattedRow['height_participant_image'];
221 }
222 }
223
224 $this->pdf->SetLineStyle([
225 'width' => 0.1,
226 'cap' => 'round',
227 'join' => 'round',
228 'dash' => '2,2',
229 'color' => [0, 0, 200],
230 ]);
231
232 $rowCount = CRM_Badge_Form_Layout::FIELD_ROWCOUNT;
233 for ($i = 1; $i <= $rowCount; $i++) {
234 if (!empty($formattedRow['token'][$i]['token'])) {
235 $value = '';
236 if ($formattedRow['token'][$i]['token'] != 'spacer') {
237 $value = $formattedRow['token'][$i]['value'];
238 }
239
240 $xAlign = $x;
241 $rowWidth = $this->pdf->width;
242 if (!empty($formattedRow['participant_image']) && !empty($formattedRow['width_participant_image'])) {
243 $rowWidth = $this->pdf->width - $formattedRow['width_participant_image'];
244 if ($formattedRow['alignment_participant_image'] == 'L') {
245 $xAlign = $x + $formattedRow['width_participant_image'] + $imageAlign;
246 }
247 }
248 $offset = $this->pdf->getY() + $startOffset + $cellspacing;
249
250 $this->pdf->SetFont($formattedRow['token'][$i]['font_name'], $formattedRow['token'][$i]['font_style'],
251 $formattedRow['token'][$i]['font_size']);
252 $this->pdf->MultiCell($rowWidth, 0, $value,
253 $this->border, $formattedRow['token'][$i]['text_alignment'], 0, 1, $xAlign, $offset);
254
255 // set this to zero so that it is added only for first element
256 $startOffset = 0;
257 }
258 }
259
260 if (!empty($formattedRow['barcode'])) {
261 $data = $formattedRow['values'];
262
263 if ($formattedRow['barcode']['type'] == 'barcode') {
264 $data['current_value'] = $formattedRow['values']['contact_id'] . '-' . $formattedRow['values']['participant_id'];
265 }
266 else {
267 // view participant url
268 $data['current_value'] = CRM_Utils_System::url('civicrm/contact/view/participant',
269 'action=view&reset=1&cid=' . $formattedRow['values']['contact_id'] . '&id='
270 . $formattedRow['values']['participant_id'],
271 TRUE,
272 NULL,
273 FALSE
274 );
275 }
276
277 // call hook alterBarcode
278 CRM_Utils_Hook::alterBarcode($data, $formattedRow['barcode']['type']);
279
280 if ($formattedRow['barcode']['type'] == 'barcode') {
281 // barcode position
282 $xAlign = $x;
283
284 switch ($formattedRow['barcode']['alignment']) {
285 case 'L':
286 $xAlign += -14;
287 break;
288
289 case 'R':
290 $xAlign += 27;
291 break;
292
293 case 'C':
294 $xAlign += 9;
295 break;
296 }
297
298 $style = [
299 'position' => '',
300 'align' => '',
301 'stretch' => FALSE,
302 'fitwidth' => TRUE,
303 'cellfitalign' => '',
304 'border' => FALSE,
305 'hpadding' => 13.5,
306 'vpadding' => 'auto',
307 'fgcolor' => [0, 0, 0],
308 'bgcolor' => FALSE,
309 'text' => FALSE,
310 'font' => 'helvetica',
311 'fontsize' => 8,
312 'stretchtext' => 0,
313 ];
314
315 $this->pdf->write1DBarcode($data['current_value'], 'C128', $xAlign, $y + $this->pdf->height - 10, '70',
316 12, 0.4, $style, 'B');
317 }
318 else {
319 // qr code position
320 $xAlign = $x;
321
322 switch ($formattedRow['barcode']['alignment']) {
323 case 'L':
324 $xAlign += -5;
325 break;
326
327 case 'R':
328 $xAlign += 56;
329 break;
330
331 case 'C':
332 $xAlign += 29;
333 break;
334 }
335
336 $style = [
337 'border' => FALSE,
338 'hpadding' => 13.5,
339 'vpadding' => 'auto',
340 'fgcolor' => [0, 0, 0],
341 'bgcolor' => FALSE,
342 'position' => '',
343 ];
344
345 $this->pdf->write2DBarcode($data['current_value'], 'QRCODE,H', $xAlign, $y + $this->pdf->height - 26, 30,
346 30, $style, 'B');
347 }
348 }
349 }
350
351 /**
352 * Helper function to print images.
353 *
354 * @param string $img
355 * Image url.
356 *
357 * @param string $x
358 * @param string $y
359 * @param null $w
360 * @param null $h
361 */
362 public function printImage($img, $x = '', $y = '', $w = NULL, $h = NULL) {
363 if (!$x) {
364 $x = $this->pdf->GetAbsX();
365 }
366
367 if (!$y) {
368 $y = $this->pdf->GetY();
369 }
370
371 $this->imgRes = 300;
372
373 if ($img) {
374 [$w, $h] = self::getImageProperties($img, $this->imgRes, $w, $h);
375 $this->pdf->Image($img, $x, $y, $w, $h, '', '', '', FALSE, 72, '', FALSE,
376 FALSE, $this->debug, FALSE, FALSE, FALSE);
377 }
378 $this->pdf->SetXY($x, $y);
379 }
380
381 /**
382 * @param $img
383 * @param int $imgRes
384 * @param null $w
385 * @param null $h
386 *
387 * @return array
388 */
389 public static function getImageProperties($img, $imgRes = 300, $w = NULL, $h = NULL) {
390 $imgsize = getimagesize($img);
391 $f = $imgRes / 25.4;
392 $w = !empty($w) ? $w : $imgsize[0] / $f;
393 $h = !empty($h) ? $h : $imgsize[1] / $f;
394 return [$w, $h];
395 }
396
397 /**
398 * Build badges parameters before actually creating badges.
399 *
400 * @param array $params
401 * Associated array of submitted values.
402 * @param CRM_Core_Form $form
403 */
404 public static function buildBadges(&$params, &$form) {
405 // get name badge layout info
406 $layoutInfo = CRM_Badge_BAO_Layout::buildLayout($params);
407 $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), ['schema' => ['participantId'], 'smarty' => FALSE]);
408 // split/get actual field names from token and individual contact image URLs
409 $returnProperties = $processorTokens = [];
410 if (!empty($layoutInfo['data']['token'])) {
411 foreach ($layoutInfo['data']['token'] as $index => $value) {
412 if ($value) {
413 $token = CRM_Utils_Token::getTokens($value);
414 if (strpos($value, '{event.') === 0) {
415 $element = $token['event'][0];
416 //FIX ME - we need to standardize event token names
417 if (substr($element, 0, 6) != 'event_') {
418 // legacy style.
419 $element = 'event_' . $element;
420 $returnProperties[$element] = 1;
421 // add actual field name to row element
422 $layoutInfo['data']['rowElements'][$index] = $element;
423 }
424 }
425 else {
426 $tokenName = str_replace(['}', '{contact.', '{participant.'], '', $value);
427 $tokenProcessor->addMessage($tokenName, $value, 'text/plain');
428 $processorTokens[] = $tokenName;
429 $layoutInfo['data']['rowElements'][$index] = $tokenName;
430 }
431 }
432 }
433 }
434
435 // add additional required fields for query execution
436 $additionalFields = ['participant_id', 'event_id', 'contact_id'];
437 foreach ($additionalFields as $field) {
438 $returnProperties[$field] = 1;
439 }
440
441 if ($form->_single) {
442 $queryParams = NULL;
443 }
444 else {
445 $queryParams = $form->get('queryParams');
446 }
447
448 $query = new CRM_Contact_BAO_Query($queryParams, $returnProperties, NULL, FALSE, FALSE,
449 CRM_Contact_BAO_Query::MODE_EVENT
450 );
451
452 [$select, $from, $where, $having] = $query->query();
453 if (empty($where)) {
454 $where = "WHERE {$form->_componentClause}";
455 }
456 else {
457 $where .= " AND {$form->_componentClause}";
458 }
459
460 $sortOrder = NULL;
461 if ($form->get(CRM_Utils_Sort::SORT_ORDER)) {
462 $sortOrder = $form->get(CRM_Utils_Sort::SORT_ORDER);
463 if (!empty($sortOrder)) {
464 $sortOrder = " ORDER BY $sortOrder";
465 }
466 }
467 $queryString = "$select $from $where $having $sortOrder";
468
469 $dao = CRM_Core_DAO::executeQuery($queryString);
470 $rows = [];
471
472 while ($dao->fetch()) {
473 $tokenProcessor->addRow(['contactId' => $dao->contact_id, 'participantId' => $dao->participant_id]);
474 $rows[$dao->participant_id] = [];
475 foreach ($returnProperties as $key => $dontCare) {
476 // we are now only resolving the 4 event tokens here.
477 $rows[$dao->participant_id][$key] = $dao->$key ?? NULL;
478 }
479 }
480 $tokenProcessor->evaluate();
481 foreach ($tokenProcessor->getRows() as $row) {
482 foreach ($processorTokens as $processorToken) {
483 $rows[$row->context['participantId']][$processorToken] = $row->render($processorToken);
484 }
485 }
486
487 $eventBadgeClass = new CRM_Badge_BAO_Badge();
488 $eventBadgeClass->createLabels($rows, $layoutInfo);
489 }
490
491 }