entityManager->getEntityById(User::ENTITY_TYPE, $userId); if (!$user) { throw new RuntimeException('Report Sending Builder: No User with id = ' . $userId); } /** @var ?Preferences $preferences */ $preferences = $this->entityManager->getEntityById(Preferences::ENTITY_TYPE, $userId); $this->preferences = $preferences; $this->language = $this->injectableFactory->createWith(Language::class, [ 'language' => $this->getPreference('language'), 'useCache' => $this->config->get('useCache'), ]); $this->dateTime = new DateTime( $this->getPreference('dateFormat'), $this->getPreference('timeFormat'), $this->getPreference('timeZone') ); } /** * @return mixed */ private function getPreference(string $attribute) { if ( $this->preferences->get($attribute) !== null && $this->preferences->get($attribute) !== '' ) { $this->preferences->get($attribute); } return $this->config->get($attribute); } /** * @param stdClass $data * @param GridResult|array $reportResult * @param bool $isLocal Images will be included. * @throws Error */ public function buildEmailData($data, $reportResult, Report $report, bool $isLocal = false): void { if (!is_object($data) || !isset($data->userId)) { throw new RuntimeException('Report Sending Builder: Not enough data for sending email.'); } $this->initForUserById($data->userId); $type = $report->getType(); switch ($type) { case Report::TYPE_GRID: case Report::TYPE_JOINT_GRID: assert($reportResult instanceof GridResult); $this->buildEmailGridData($data, $reportResult, $report); break; case Report::TYPE_LIST: assert(is_array($reportResult)); $this->buildEmailListData($data, $reportResult, $report, $isLocal); break; } } /** * @param stdClass $data * @throws Error */ private function buildEmailGridData($data, GridResult $reportResult, Report $report): void { $depth = count($reportResult->getGroupByList()); if ($depth === 2) { if ($reportResult->getTableMode() === Data::TABLE_MODE_NORMALIZED) { $this->buildEmailGrid2NormalizedData($data, $reportResult, $report); return; } $this->buildEmailGrid2Data($data, $reportResult, $report); return; } $this->buildEmailGrid1Data($data, $reportResult, $report); } /** * @param stdClass $data * @param array $reportResult * @throws Error */ private function buildEmailListData($data, array $reportResult, Report $report, bool $isLocal): void { $entityType = $report->getTargetEntityType(); $columns = $report->getColumns(); $columnsData = get_object_vars($report->getColumnsData()); $fields = $this->metadata->get(['entityDefs', $entityType, 'fields']); $columnAttributes = []; foreach ($columns as $column) { $columnData = $columnsData[$column] ?? null; $attrs = []; if (is_object($columnData)) { if (isset($columnData->width)) { $attrs['width'] = $columnData->width . '%'; } if (isset($columnData->align)) { $attrs['align'] = $columnData->align; } } $columnAttributes[$column] = $attrs; } $columnTitles = []; foreach ($columns as $column) { $field = $column; $scope = $entityType; $isForeign = false; if (str_contains($column, '.')) { $isForeign = true; [$link, $field] = explode('.', $column); $scope = $this->metadata->get(['entityDefs', $entityType, 'links', $link, 'entity']); $fields[$column] = $this->metadata->get(['entityDefs', $scope, 'fields', $field]); $fields[$column]['scope'] = $scope; $fields[$column]['isForeign'] = true; } $label = $this->language->translateLabel($field, 'fields', $scope); if ($isForeign) { $label = $this->language->translateLabel($link ?? '', 'links', $entityType) . ' . ' . $label; } $columnTitles[] = [ 'label' => $label, 'attrs' => $columnAttributes[$column], 'value' => $label, 'isBold' => true, ]; } $rows = []; foreach ($reportResult as $recordKey => $record) { foreach ($columns as $columnKey => $column) { $type = (isset($fields[$column])) ? $fields[$column]['type'] : ''; $columnInRecord = str_replace('.', '_', $column); $value = $record[$columnInRecord] ?? null; $value = is_scalar($value) ? (string) $record[$columnInRecord] : ''; switch ($type) { case FieldType::DATE: if (!empty($value)) { $value = $this->dateTime->convertSystemDate($value); } break; case FieldType::DATETIME: case FieldType::DATETIME_OPTIONAL: if (!empty($value)) { $value = $this->dateTime->convertSystemDateTime($value); } break; case FieldType::LINK: case FieldType::LINK_PARENT: case FieldType::LINK_ONE: if (!empty($record[$columnInRecord . 'Name'])) { $value = $record[$columnInRecord . 'Name']; } break; case FieldType::LINK_MULTIPLE: case FieldType::ATTACHMENT_MULTIPLE: if (!empty($record[$columnInRecord . 'Names'])) { $value = implode(', ', array_values( (array) $record[$columnInRecord . 'Names'])); } break; case 'jsonArray': break; case FieldType::BOOL: $value = ($value) ? '1' : '0'; break; case FieldType::ENUM: if (isset($fields[$column]['isForeign']) && $fields[$column]['isForeign']) { [, $field] = explode('.', $column); $value = $this->language->translateOption($value, $field, $fields[$column]['scope']); } else { $value = $this->language->translateOption($value, $column, $entityType); } break; case FieldType::INT: $value = $this->formatInt($value); break; case FieldType::FLOAT: case 'decimal': $isCurrency = isset($fields[$column]['view']) && // @todo Refactor. strpos($fields[$column]['view'], 'currency-converted'); $decimalPlaces = $fields[$column]['decimalPlaces'] ?? null; $value = ($isCurrency) ? $this->formatCurrency($value, null, $decimalPlaces) : $this->formatFloat($value, $decimalPlaces); break; case FieldType::CURRENCY: $decimalPlaces = $fields[$column]['decimalPlaces'] ?? null; $value = $this->formatCurrency($value, $record[$columnInRecord . 'Currency'], $decimalPlaces); break; case FieldType::CURRENCY_CONVERTED: $decimalPlaces = $fields[$column]['decimalPlaces'] ?? null; $value = $this->formatCurrency($value, null, $decimalPlaces); break; case FieldType::ADDRESS: $value = ''; if (!empty($record[$columnInRecord . 'Street'])) { $value = $record[$columnInRecord.'Street']; } if ( !empty($record[$columnInRecord.'City']) || !empty($record[$columnInRecord.'State']) || !empty($record[$columnInRecord.'PostalCode']) ) { if ($value) { $value .= " "; } if (!empty($record[$columnInRecord.'City'])) { $value .= $record[$columnInRecord.'City']; if ( !empty($record[$columnInRecord.'State']) || !empty($record[$columnInRecord.'PostalCode']) ) { $value .= ', '; } } if (!empty($record[$columnInRecord.'State'])) { $value .= $record[$columnInRecord.'State']; if (!empty($record[$columnInRecord.'PostalCode'])) { $value .= ' '; } } if (!empty($record[$columnInRecord.'PostalCode'])) { $value .= $record[$columnInRecord.'PostalCode']; } } if (!empty($record[$columnInRecord.'Country'])) { if ($value) { $value .= " "; } $value .= $record[$columnInRecord.'Country']; } break; case FieldType::ARRAY: case FieldType::MULTI_ENUM: case FieldType::CHECKLIST: $value = $record[$columnInRecord] ?? []; if (is_array($value)) { $value = implode(", ", $value); } else { $value = ''; } break; case FieldType::IMAGE: if (!$isLocal) { break; } $attachmentId = $record[$columnInRecord . 'Id'] ?? null; if ($attachmentId) { /** @var ?Attachment $attachment */ $attachment = $this->entityManager ->getEntityById(Attachment::ENTITY_TYPE, $attachmentId); if ($attachment) { $filePath = $this->fileStorageManager->getLocalFilePath($attachment); $value = "\"image\""; } } break; } $rows[$recordKey][$columnKey] = [ 'value' => $value, 'attrs' => $columnAttributes[$column], ]; } } $bodyData = [ 'columnList' => $columnTitles, 'rowList' => $rows, 'noDataLabel' => $this->language->translateLabel('No Data'), ]; try { $subject = $this->renderReport($report, 'reportSendingList', 'subject'); $body = $this->renderReport($report, 'reportSendingList', 'body', $bodyData); } catch (Exception $e) { $this->log->error($e->getMessage()); throw Error::createWithBody( 'emailTemplateParsingError', Error\Body::create() ->withMessageTranslation('emailTemplateParsingError', 'Report', ['template' => 'reportSendingList']) ->encode() ); } $data->emailSubject = $subject; $data->emailBody = $body; $data->tableData = array_merge([$columnTitles], $rows); } /** * @param stdClass $data * @throws Error */ private function buildEmailGrid1Data($data, GridResult $reportResult, Report $report): void { $reportData = $reportResult->getReportData(); /** @var array $columnDecimalPlacesMap */ $columnDecimalPlacesMap = get_object_vars($reportResult->getColumnDecimalPlacesMap()); $groupCount = count($reportResult->getGroupByList()); if ($groupCount) { $groupName = $reportResult->getGroupByList()[0]; } else { $groupName = '__STUB__'; } $rows = []; $row = []; $row[] = ['value' => '']; $columnTypes = []; $hasSubListColumns = count($reportResult->getSubListColumnList()) > 0; foreach ($reportResult->getColumnList() as $column) { $allowedTypeList = [ 'int', 'float', 'decimal', 'currency', 'currencyConverted', ]; $columnType = null; $function = null; if (strpos($column, ':')) { [$function, $field] = explode(':', $column); } else { $field = $column; } $fieldEntityType = $report->getTargetEntityType(); $columnType = $reportResult->getColumnTypeMap()[$column] ?? null; if (strpos($column, '.')) { [$link, $field] = explode('.', $column); $fieldEntityType = $this->metadata->get(['entityDefs', $fieldEntityType, 'links', $link, 'entity']); } if (!$columnType) { if ($function === 'COUNT') { $columnType = 'int'; } else { $columnType = $this->metadata->get(['entityDefs', $fieldEntityType, 'fields', $field, 'type']); } } $columnTypes[$column] = (in_array($columnType, $allowedTypeList)) ? $columnType : 'float'; $label = $column; if (isset($reportResult->getColumnNameMap()[$column])) { $label = $reportResult->getColumnNameMap()[$column]; } $row[] = [ 'value' => $label, 'isBold' => true, ]; } $rows[] = $row; foreach ($reportResult->getGrouping()[0] ?? [] as $gr) { $rows[] = $this->buildEmailGrid1GroupingRow( gr: $gr, groupName: $groupName, reportData: $reportData, reportResult: $reportResult, columnTypes: $columnTypes, columnDecimalPlacesMap: $columnDecimalPlacesMap, ); if ($hasSubListColumns) { $rows = array_merge( $rows, $this->buildEmailGrid1SubListRowList( gr: $gr, reportResult: $reportResult, columnTypes: $columnTypes, columnDecimalPlacesMap: $columnDecimalPlacesMap, ) ); $rows[] = $this->buildEmailGrid1GroupingRow( gr: $gr, groupName: $groupName, reportData: $reportData, reportResult: $reportResult, columnTypes: $columnTypes, columnDecimalPlacesMap: $columnDecimalPlacesMap, onlyNumeric: true, ); } } if ($groupCount) { $row = []; $totalLabel = $this->language->translateLabel('Total', 'labels', 'Report'); $row[] = [ 'value' => $totalLabel, 'isBold' => true, ]; foreach ($reportResult->getColumnList() as $column) { if ( in_array($column, $reportResult->getNumericColumnList()) && in_array($column, $reportResult->getAggregatedColumnList()) ) { $sum = 0; if (isset($reportResult->getSums()->$column)) { $sum = $reportResult->getSums()->$column; switch ($columnTypes[$column]) { case 'int': $sum = $this->formatInt($sum); break; case 'float': case 'decimal': $decimalPlaces = $columnDecimalPlacesMap[$column] ?? null; $sum = $this->formatFloat($sum, $decimalPlaces); break; case 'currency': case 'currencyConverted': $decimalPlaces = $columnDecimalPlacesMap[$column] ?? null; $sum = $this->formatCurrency($sum, $reportResult->getCurrency(), $decimalPlaces); break; } } } else { $sum = ''; } $row[] = [ 'value' => $sum, 'isBold' => true, 'attrs' => ['align' => 'right'], ]; } $rows[] = $row; } else { foreach ($rows as &$row) { unset($row[0]); $row = array_values($row); } } $this->prepareGrid1Result($rows, $report, $data); } /** * @param array $columnTypes * @param array $columnDecimalPlacesMap * @return (array)[] */ private function buildEmailGrid1GroupingRow( string $gr, string $groupName, object $reportData, GridResult $reportResult, array $columnTypes, array $columnDecimalPlacesMap, bool $onlyNumeric = false, ): array { $row = []; $hasSubListColumns = count($reportResult->getSubListColumnList()) > 0; $label = $gr; if (empty($label)) { $label = $this->language->translateLabel('-Empty-', 'labels', 'Report'); } else if (isset($reportResult->getGroupValueMap()[$groupName][$gr])) { $label = $reportResult->getGroupValueMap()[$groupName][$gr]; } if (strpos($groupName , ':')) { [$function,] = explode(':', $groupName); $label = $this->handleDateGroupValue($function, $label); } if ($hasSubListColumns && $onlyNumeric) { $label = $this->language->translateLabel('Group Total', 'labels', 'Report'); } $row[] = [ 'value' => $label, 'isBold' => $hasSubListColumns && !$onlyNumeric, ]; foreach ($reportResult->getColumnList() as $column) { $isNumericValue = in_array($column, $reportResult->getNumericColumnList()); if ($hasSubListColumns && !$onlyNumeric && $isNumericValue) { $row[] = []; continue; } if ($hasSubListColumns && $onlyNumeric && !$isNumericValue) { $row[] = []; continue; } if ( $isNumericValue && !in_array($column, $reportResult->getAggregatedColumnList()) ) { $row[] = []; continue; } if ($isNumericValue) { $value = $this->formatColumnValue( value: $reportData->$gr->$column ?? 0, type: $columnTypes[$column], decimalPlaces: $columnDecimalPlacesMap[$column] ?? null, currency: $reportResult->getCurrency(), ); $align = 'right'; } else { $value = $reportData->$gr->$column ?? null; if ($value !== null) { $value = $reportResult->getCellValueMaps()->$column->$value ?? $value; } $align = 'left'; } $row[] = [ 'value' => $value, 'attrs' => [ 'align' => $align, ], ]; } return $row; } private function formatColumnValue( mixed $value, ?string $type, ?int $decimalPlaces = null, ?string $currency = null, ): string { if ((!$type || $type === 'int') && $decimalPlaces !== null) { $type = 'float'; } return match ($type) { 'int' => $this->formatInt($value), 'float' => $this->formatFloat($value, $decimalPlaces), 'currency', 'currencyConverted' => $this->formatCurrency($value, $currency, $decimalPlaces), default => (string) $value, }; } /** * @param array $columnTypes * @param array $columnDecimalPlacesMap * @return array{value: mixed, isBold?: bool, attrs?: array}[][] */ private function buildEmailGrid1SubListRowList( string $gr, GridResult $reportResult, array $columnTypes, array $columnDecimalPlacesMap, ): array { $itemList = $reportResult->getSubListData()->$gr; $rowList = []; foreach ($itemList as $item) { $rowList[] = $this->buildEmailGrid1SubListRow( item: $item, reportResult: $reportResult, columnTypes: $columnTypes, columnDecimalPlacesMap: $columnDecimalPlacesMap, ); } return $rowList; } /** * @param array $columnTypes * @param array $columnDecimalPlacesMap * @return array{value: mixed, isBold?: bool, attrs?: array}[] */ private function buildEmailGrid1SubListRow( object $item, GridResult $reportResult, array $columnTypes, array $columnDecimalPlacesMap ): array { $row = []; $row[] = [ 'value' => '', ]; foreach ($reportResult->getColumnList() as $column) { if (!in_array($column, $reportResult->getSubListColumnList())) { $row[] = [ 'value' => '', ]; continue; } $isNumericValue = in_array($column, $reportResult->getNumericColumnList()); if ($isNumericValue) { $value = $this->formatColumnValue( value: $item->$column ?? 0, type: $columnTypes[$column], decimalPlaces: $columnDecimalPlacesMap[$column] ?? null ); } else { $value = $item->$column ?? ''; } $row[] = [ 'value' => $value, 'attrs' => ['align' => $isNumericValue ? 'right' : 'left'], ]; } return $row; } /** * @param stdClass $data * @throws Error */ private function buildEmailGrid2NormalizedData($data, GridResult $reportResult, Report $report): void { $groupName1 = $reportResult->getGroupByList()[0]; $groupName2 = $reportResult->getGroupByList()[1]; /** @var array $columnDecimalPlacesMap */ $columnDecimalPlacesMap = get_object_vars($reportResult->getColumnDecimalPlacesMap()); $rows = []; $row = [ [ 'value' => $reportResult->getGroupNameMap()[$groupName1] ?? null, 'isBold' => true, ], [ 'value' => $reportResult->getGroupNameMap()[$groupName2] ?? null, 'isBold' => true, ], ]; foreach ($reportResult->getNonSummaryColumnList() as $column) { $row[] = [ 'value' => $reportResult->getColumnNameMap()[$column] ?? null, 'isBold' => true, ]; } foreach ($reportResult->getSummaryColumnList() as $column) { $row[] = [ 'value' => $reportResult->getColumnNameMap()[$column] ?? null, 'isBold' => true, ]; } $rows[] = $row; foreach ($reportResult->getGrouping()[0] ?? [] as $group) { foreach ($reportResult->getGrouping()[1] ?? [] as $secondGroup) { $row = []; $row[] = [ 'value' => $this->prepareGroupLabel($reportResult, $groupName1, $group), ]; $row[] = [ 'value' => $this->prepareGroupLabel($reportResult, $groupName2, $secondGroup), ]; foreach ($reportResult->getNonSummaryColumnList() as $column) { $columnGroup = $reportResult->getNonSummaryColumnGroupMap()->$column ?? null; $columnGroupValue = $columnGroup === $reportResult->getGroupByList()[0] ? $group : $secondGroup; $value = $this->gridExportService->getCellDisplayValueFromResult( groupIndex: $columnGroup === $reportResult->getGroupByList()[0] ? 0 : 1, groupValue: $columnGroupValue, column: $column, reportResult: $reportResult, ); [$value, $align] = $this->prepareColumnDisplayData( reportResult: $reportResult, column: $column, value: $value, ); $row[] = [ 'value' => $value, 'attrs' => ['align' => $align], ]; } $hasNonEmpty = false; foreach ($reportResult->getSummaryColumnList() as $column) { $value = $reportResult->getReportData()->$group->$secondGroup->$column ?? null; if ($value !== null) { $hasNonEmpty = true; } $value = $this->formatColumnValue( value: $value, type: $this->getNumericColumnType($column, $report), decimalPlaces: $columnDecimalPlacesMap[$column] ?? null, currency: $reportResult->getCurrency(), ); $row[] = [ 'value' => $value, 'attrs' => ['align' => 'right'], ]; } if (!$hasNonEmpty) { continue; } $rows[] = $row; } } $row = []; $row[] = [ 'value' => $this->language->translateLabel('Total', 'labels', 'Report'), 'isBold' => true, ]; $row[] = []; foreach ($reportResult->getNonSummaryColumnList() as $ignored) { $row[] = []; } foreach ($reportResult->getSummaryColumnList() as $column) { $sum = $reportResult->getSums()->$column ?? 0; $value = $this->formatColumnValue( value: $sum , type: $this->getNumericColumnType($column, $report), decimalPlaces: $columnDecimalPlacesMap[$column] ?? null, currency: $reportResult->getCurrency(), ); $row[] = [ 'value' => $value, 'isBold' => true, 'attrs' => ['align' => 'right'], ]; } $rows[] = $row; $this->prepareGrid1Result($rows, $report, $data); } /** * @param stdClass $data * @throws Error */ private function buildEmailGrid2Data($data, GridResult $reportResult, Report $report): void { $reportData = $reportResult->getReportData(); /** @var array $columnDecimalPlacesMap */ $columnDecimalPlacesMap = get_object_vars($reportResult->getColumnDecimalPlacesMap()); $specificColumn = $data->specificColumn ?? null; $grids = []; foreach ($reportResult->getSummaryColumnList() as $column) { $groupName1 = $reportResult->getGroupByList()[0]; $groupName2 = $reportResult->getGroupByList()[1]; if ($specificColumn && $specificColumn !== $column) { continue; } $group1NonSummaryColumnList = $reportResult->getGroup1NonSummaryColumnList() ?? []; $group2NonSummaryColumnList = $reportResult->getGroup2NonSummaryColumnList() ?? []; $columnType = $this->getNumericColumnType($column, $report); $columnTypes[$column] = $columnType; $grid = []; $row = []; $row[] = []; foreach ($group2NonSummaryColumnList as $c) { $text = $reportResult->getColumnNameMap()[$c]; $row[] = ['value' => $text]; } foreach ($reportResult->getGrouping()[0] ?? [] as $gr1) { $label = $this->prepareGroupLabel($reportResult, $groupName1, $gr1); $row[] = ['value' => $label]; } if ($reportResult->getGroup2Sums() !== null) { $row[] = [ 'value' => $this->language->translateLabel('Total', 'labels', 'Report'), 'isBold' => true, ]; } $grid[] = $row; foreach ($group1NonSummaryColumnList as $c) { $row = []; $text = $reportResult->getColumnNameMap()[$c]; $row[] = ['value' => $text]; foreach ($group2NonSummaryColumnList as $ignored3) { $row[] = []; } foreach ($reportResult->getGrouping()[0] ?? [] as $gr1) { $value = $this->gridExportService->getCellDisplayValueFromResult( groupIndex: 0, groupValue: $gr1, column: $c, reportResult: $reportResult, ); [$value, $align] = $this->prepareColumnDisplayData( reportResult: $reportResult, column: $c, value: $value, ); $row[] = [ 'value' => $value, 'attrs' => ['align' => $align], ]; } $row[] = []; $grid[] = $row; } foreach ($reportResult->getGrouping()[1] ?? [] as $gr2) { $row = []; $label = $gr2; if (empty($label)) { $label = $this->language->translateLabel('-Empty-', 'labels', 'Report'); } else if (isset($reportResult->getGroupValueMap()[$groupName2][$gr2])) { $label = $reportResult->getGroupValueMap()[$groupName2][$gr2]; } if (strpos($groupName2 , ':')) { [$function, $field] = explode(':', $groupName2); $label = $this->handleDateGroupValue($function, $label); } $row[] = [ 'value' => $label, 'isBold' => true, ]; foreach ($group2NonSummaryColumnList as $c) { $value = $this->gridExportService->getCellDisplayValueFromResult( groupIndex: 1, groupValue: $gr2, column: $c, reportResult: $reportResult, ); [$value, $align] = $this->prepareColumnDisplayData( reportResult: $reportResult, column: $c, value: $value, ); $row[] = [ 'value' => $value, 'attrs' => ['align' => $align], ]; } foreach ($reportResult->getGrouping()[0] ?? [] as $gr1) { $value = 0; if (isset($reportData->$gr1->$gr2->$column)) { $value = $reportData->$gr1->$gr2->$column; $value = $this->formatColumnValue( value: $value, type: $columnTypes[$column], decimalPlaces: $columnDecimalPlacesMap[$column] ?? null, currency: $reportResult->getCurrency(), ); } $row[] = [ 'value' => $value, 'attrs' => ['align' => 'right'], ]; } if ($reportResult->getGroup2Sums() !== null) { $value = $reportResult->getGroup2Sums()->$gr2->$column; $value = $this->formatColumnValue( value: $value ?? 0, type: $columnTypes[$column], decimalPlaces: $columnDecimalPlacesMap[$column] ?? null, currency: $reportResult->getCurrency(), ); $row[] = [ 'value' => $value, 'attrs' => ['align' => 'right'], 'isBold' => true, ]; } $grid[] = $row; } $row = []; $row[] = [ 'value' => $this->language->translateLabel('Total', 'labels', 'Report'), 'isBold' => true, ]; foreach ($group2NonSummaryColumnList as $ignored) { $row[] = ['value' => '']; } foreach ($reportResult->getGrouping()[0] ?? [] as $gr1) { $sum = 0; if ( !empty($reportResult->getGroup1Sums()->$gr1) && !empty($reportResult->getGroup1Sums()->$gr1->$column) ) { $sum = $reportResult->getGroup1Sums()->$gr1->$column; $sum = $this->formatColumnValue( value: $sum , type: $columnTypes[$column], decimalPlaces: $columnDecimalPlacesMap[$column] ?? null, currency: $reportResult->getCurrency(), ); } $row[] = [ 'value' => $sum, 'isBold' => true, 'attrs' => ['align' => 'right'], ]; } if ($reportResult->getGroup2Sums() !== null) { $value = $reportResult->getSums()->$column; $value = $this->formatColumnValue( value: $value ?? 0, type: $columnTypes[$column], decimalPlaces: $columnDecimalPlacesMap[$column] ?? null, currency: $reportResult->getCurrency(), ); $row[] = [ 'value' => $value, 'attrs' => ['align' => 'right'], 'isBold' => true, ]; } $grid[] = $row; $rows = $grid; $grids[] = [ 'rowList' => $rows, 'header' => $reportResult->getColumnNameMap()[$column], ]; } $bodyData = ['gridList' => $grids]; try { $subject = $this->renderReport($report, 'reportSendingGrid2', 'subject'); $body = $this->renderReport($report, 'reportSendingGrid2', 'body', $bodyData); } catch (Exception $e) { $this->log->error($e->getMessage()); throw Error::createWithBody( 'emailTemplateParsingError', Error\Body::create() ->withMessageTranslation('emailTemplateParsingError', 'Report', ['template' => 'reportSendingGrid2']) ->encode() ); } $data->emailSubject = $subject; $data->emailBody = $body; if (count($grids)) { $data->tableData = $grids[0]['rowList']; } else { $data->tableData = []; } } public function sendEmail( string $userId, string $emailSubject, string $emailBody, ?string $attachmentId, ): void { if (!$emailSubject || !$emailBody) { throw new RuntimeException('Not enough data for sending report email.'); } $user = $this->entityManager->getEntity('User', $userId); if (!$user) { throw new RuntimeException('Report Sending Builder[sendEmail]: No user with id ' . $userId); } $emailAddress = $user->get('emailAddress'); if (!$emailAddress) { throw new RuntimeException('Report Sending Builder[sendEmail]: User has no email address'); } /** @var Email $email */ $email = $this->entityManager->getNewEntity(Email::ENTITY_TYPE); $email->set([ 'to' => $emailAddress, 'subject' => $emailSubject, 'body' => $emailBody, 'isHtml' => true, ]); $attachmentList = []; if ($attachmentId) { $attachment = $this->entityManager->getEntityById(Attachment::ENTITY_TYPE, $attachmentId); if ($attachment) { $attachmentList[] = $attachment; } } try { $this->emailSender ->create() ->withAttachments($attachmentList) ->send($email); } catch (Exception $e) { if (isset($attachment)) { $this->entityManager->removeEntity($attachment); } throw new RuntimeException("Report Email Sending:" . $e->getMessage()); } if (isset($attachment)) { $this->entityManager->removeEntity($attachment); } } /** * @param string|numeric $value */ private function formatCurrency($value, ?string $currency = null, ?int $decimalPlaces = null): string { if ($value === "") { return $value; } $userThousandSeparator = $this->getPreference('thousandSeparator'); $userDecimalMark = $this->getPreference('decimalMark'); // @todo Revise. $currencyFormat = (int) $this->config->get('currencyFormat'); if (!$currency) { // @todo Support currency defined in the report result. $currency = $this->config->get('defaultCurrency'); } if ($currencyFormat) { $pad = $decimalPlaces ?? ((int) $this->config->get('currencyDecimalPlaces')); $value = number_format(floatval($value), $pad, $userDecimalMark, $userThousandSeparator); } else { $value = $this->formatFloat($value, $decimalPlaces); } switch ($currencyFormat) { case 1: $value = $value . ' ' . $currency; break; case 2: $currencySign = $this->metadata->get(['app', 'currency', 'symbolMap', $currency]); $value = $currencySign . $value; break; case 3: $currencySign = $this->metadata->get(['app', 'currency', 'symbolMap', $currency]); $value = $value . ' ' . $currencySign; break; } return $value; } /** * @param string|int $value */ private function formatInt($value): string { if ($value === '') { return $value; } $userThousandSeparator = $this->getPreference('thousandSeparator'); $userDecimalMark = $this->getPreference('decimalMark'); return number_format(floatval($value), 0, $userDecimalMark, $userThousandSeparator); } /** * @param string|float|int $value */ private function formatFloat($value, ?int $decimalPlaces = null): string { if ($value === '') { return $value; } if ($value === 0 || $value === 0.0) { return '0'; } $userThousandSeparator = $this->getPreference('thousandSeparator'); $userDecimalMark = $this->getPreference('decimalMark'); $valueString = number_format(floatval($value), $decimalPlaces ?? 8, $userDecimalMark, $userThousandSeparator); if ($decimalPlaces !== null) { return $valueString; } return rtrim(rtrim($valueString, '0'), $userDecimalMark); } /** * @param array $data */ private function renderReport( Report $entity, string $templateName, string $type, array $data = [] ): string { $template = $this->templateFileManager->getTemplate($templateName, $type, null, 'Advanced'); $template = str_replace(["\n", "\r"], '', $template); return $this->templateRendererFactory ->create() ->setTemplate($template) ->setEntity($entity) ->setSkipInlineAttachmentHandling() ->setData($data) ->render(); } private function handleDateGroupValue(string $function, string $value): string { if ($function === 'MONTH') { [$year, $month] = explode('-', $value); $monthNamesShort = $this->language->get('Global.lists.monthNamesShort'); $monthLabel = $monthNamesShort[intval($month) - 1]; return $monthLabel . ' ' . $year; } if ($function === 'DAY') { return $this->dateTime->convertSystemDate($value); } if ($function === 'QUARTER') { return $value; } if ($function === 'YEAR_FISCAL') { return $value . '-' . ((string) ((int) $value + 1)); } return $value; } /** * @param array[][] $rows * @throws Error */ private function prepareGrid1Result(array $rows, Report $report, stdClass $data): void { $bodyData = [ 'rowList' => $rows, ]; try { $subject = $this->renderReport($report, 'reportSendingGrid1', 'subject'); $body = $this->renderReport($report, 'reportSendingGrid1', 'body', $bodyData); } catch (Exception $e) { $this->log->error($e->getMessage()); throw Error::createWithBody( 'emailTemplateParsingError', Error\Body::create() ->withMessageTranslation('emailTemplateParsingError', 'Report', ['template' => 'reportSendingGrid1']) ->encode() ); } $data->emailSubject = $subject; $data->emailBody = $body; $data->tableData = $rows; } /** * @param GridResult $reportResult */ private function prepareGroupLabel(GridResult $reportResult, string $groupName, string $group): string { $label = $group; if (empty($label)) { $label = $this->language->translateLabel('-Empty-', 'labels', 'Report'); } else if (!empty($reportResult->getGroupValueMap()[$groupName][$group])) { $label = $reportResult->getGroupValueMap()[$groupName][$group]; } if (strpos($groupName, ':')) { [$function,] = explode(':', $groupName); $label = $this->handleDateGroupValue($function, $label); } return $label; } /** * @param GridResult $reportResult * @return array{string, string} */ private function prepareColumnDisplayData( GridResult $reportResult, string $column, mixed $value, ): array { $cDat = $this->gridHelper->getDataFromColumnName( entityType: $reportResult->getEntityType(), column: $column, result: $reportResult, ); /** @var array $columnDecimalPlacesMap */ $columnDecimalPlacesMap = get_object_vars($reportResult->getColumnDecimalPlacesMap()); $align = 'left'; switch ($cDat->fieldType) { case 'int': $value = $this->formatInt($value); $align = 'right'; break; case 'float': case 'decimal': $decimalPlace = $columnDecimalPlacesMap[$column] ?? null; $value = $this->formatFloat($value, $decimalPlace); $align = 'right'; break; case 'currency': case 'currencyConverted': $decimalPlace = $columnDecimalPlacesMap[$column] ?? null; $value = $this->formatCurrency($value, null, $decimalPlace); $align = 'right'; break; } return [$value, $align]; } private function getNumericColumnType(string $column, Report $report): string { $columnType = 'int'; if (strpos($column, ':')) { [$f, $field] = explode(':', $column); if ($f !== 'COUNT') { $columnType = $this->metadata ->get(['entityDefs', $report->getTargetEntityType(), 'fields', $field, 'type']); } } if ($columnType === 'float') { // @todo Refactor. $view = $this->metadata ->get(['entityDefs', $report->getTargetEntityType(), 'fields', $field ?? $column, 'view']); if ($view && strpos($view, 'currency-converted')) { $columnType = 'currencyConverted'; } } $allowedTypeList = [ 'int', 'float', 'decimal', 'currency', 'currencyConverted', ]; if (!in_array($columnType, $allowedTypeList)) { $columnType = 'int'; } return $columnType; } }