PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.2.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.2.0
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / ReportRenderer / Pdf.php
matomo / app / core / ReportRenderer Last commit date
Csv.php 1 year ago Html.php 1 year ago Pdf.php 1 year ago Tsv.php 1 year ago
Pdf.php
442 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\ReportRenderer;
10
11 use Piwik\Common;
12 use Piwik\Filesystem;
13 use Piwik\NumberFormatter;
14 use Piwik\Piwik;
15 use Piwik\Plugins\CoreAdminHome\CustomLogo;
16 use Piwik\ReportRenderer;
17 use Piwik\TCPDF;
18 use TCPDF_FONTS;
19 /**
20 * @see libs/tcpdf
21 */
22 require_once PIWIK_INCLUDE_PATH . '/plugins/ScheduledReports/config/tcpdf_config.php';
23 /**
24 * PDF report renderer
25 */
26 class Pdf extends ReportRenderer
27 {
28 public const IMAGE_GRAPH_WIDTH_LANDSCAPE = 1050;
29 public const IMAGE_GRAPH_WIDTH_PORTRAIT = 760;
30 public const IMAGE_GRAPH_HEIGHT = 220;
31 public const LANDSCAPE = 'L';
32 public const PORTRAIT = 'P';
33 public const MAX_ROW_COUNT = 28;
34 public const TABLE_HEADER_ROW_COUNT = 6;
35 public const NO_DATA_ROW_COUNT = 6;
36 public const MAX_GRAPH_REPORTS = 3;
37 public const MAX_2COL_TABLE_REPORTS = 2;
38 public const IMPORT_FONT_PATH = 'plugins/ImageGraph/fonts/unifont.ttf';
39 public const PDF_CONTENT_TYPE = 'pdf';
40 private $reportFontStyle = '';
41 private $reportSimpleFontSize = 9;
42 private $reportHeaderFontSize = 16;
43 private $cellHeight = 6;
44 private $bottomMargin = 17;
45 private $reportWidthPortrait = 195;
46 private $reportWidthLandscape = 270;
47 private $minWidthLabelCell = 100;
48 private $maxColumnCountPortraitOrientation = 6;
49 private $logoWidth = 16;
50 private $logoHeight = 16;
51 private $totalWidth;
52 private $cellWidth;
53 private $labelCellWidth;
54 private $truncateAfter = 55;
55 private $leftSpacesBeforeLogo = 7;
56 private $logoImagePosition = array(10, 40);
57 private $headerTextColor;
58 private $reportTextColor;
59 private $tableHeaderBackgroundColor;
60 private $tableHeaderTextColor;
61 private $tableCellBorderColor;
62 private $tableBackgroundColor;
63 private $rowTopBottomBorder = array(231, 231, 231);
64 private $reportMetadata;
65 private $displayGraph;
66 private $evolutionGraph;
67 private $displayTable;
68 private $segment;
69 private $reportColumns;
70 private $reportRowsMetadata;
71 private $currentPage = 0;
72 private $reportFont = ReportRenderer::DEFAULT_REPORT_FONT_FAMILY;
73 private $TCPDF;
74 private $orientation = self::PORTRAIT;
75 public function __construct()
76 {
77 $this->TCPDF = new TCPDF();
78 $this->headerTextColor = preg_split("/,/", ReportRenderer::REPORT_TITLE_TEXT_COLOR);
79 $this->reportTextColor = preg_split("/,/", ReportRenderer::REPORT_TEXT_COLOR);
80 $this->tableHeaderBackgroundColor = preg_split("/,/", ReportRenderer::TABLE_HEADER_BG_COLOR);
81 $this->tableHeaderTextColor = preg_split("/,/", ReportRenderer::TABLE_HEADER_TEXT_COLOR);
82 $this->tableCellBorderColor = preg_split("/,/", ReportRenderer::TABLE_CELL_BORDER_COLOR);
83 $this->tableBackgroundColor = preg_split("/,/", ReportRenderer::TABLE_BG_COLOR);
84 }
85 public function setLocale($locale)
86 {
87 // WARNING
88 // To make Piwik release smaller, we're deleting some fonts from the Piwik build package.
89 // If you change this code below, make sure that the fonts are NOT deleted from the Piwik package:
90 // https://github.com/piwik/piwik-package/blob/master/scripts/build-package.sh
91 switch ($locale) {
92 case 'bn':
93 case 'hi':
94 $reportFont = 'freesans';
95 break;
96 case 'zh-tw':
97 $reportFont = 'msungstdlight';
98 break;
99 case 'ja':
100 $reportFont = 'kozgopromedium';
101 break;
102 case 'zh-cn':
103 $reportFont = 'stsongstdlight';
104 break;
105 case 'ko':
106 $reportFont = 'hysmyeongjostdmedium';
107 break;
108 case 'ar':
109 $reportFont = 'aealarabiya';
110 break;
111 case 'am':
112 case 'ta':
113 case 'th':
114 $reportFont = 'freeserif';
115 break;
116 case 'te':
117 // not working with bundled fonts
118 case 'en':
119 default:
120 $reportFont = ReportRenderer::DEFAULT_REPORT_FONT_FAMILY;
121 break;
122 }
123 // WARNING: Did you read the warning above?
124 // When user follow the FAQ https://matomo.org/faq/how-to-install/faq_142/, imported unifont font, it will apply across the entire report
125 if (is_file(self::IMPORT_FONT_PATH)) {
126 $reportFont = TCPDF_FONTS::addTTFfont(self::IMPORT_FONT_PATH, 'TrueTypeUnicode');
127 }
128 $this->reportFont = $reportFont;
129 }
130 public function sendToDisk($filename)
131 {
132 $filename = ReportRenderer::makeFilenameWithExtension($filename, self::PDF_CONTENT_TYPE);
133 $outputFilename = ReportRenderer::getOutputPath($filename);
134 $this->TCPDF->Output($outputFilename, 'F');
135 return $outputFilename;
136 }
137 public function sendToBrowserDownload($filename)
138 {
139 $filename = ReportRenderer::makeFilenameWithExtension($filename, self::PDF_CONTENT_TYPE);
140 $this->TCPDF->Output($filename, 'D');
141 }
142 public function sendToBrowserInline($filename)
143 {
144 $filename = ReportRenderer::makeFilenameWithExtension($filename, self::PDF_CONTENT_TYPE);
145 $this->TCPDF->Output($filename, 'I');
146 }
147 public function getRenderedReport()
148 {
149 return $this->TCPDF->Output('', 'S');
150 }
151 public function renderFrontPage($reportTitle, $prettyDate, $description, $reportMetadata, $segment)
152 {
153 $reportTitle = $this->formatText($reportTitle);
154 $dateRange = $this->formatText(Piwik::translate('General_DateRange') . " " . $prettyDate);
155 // footer
156 $this->TCPDF->SetFooterFont(array($this->reportFont, $this->reportFontStyle, $this->reportSimpleFontSize));
157 $this->TCPDF->SetFooterContent((strlen($reportTitle) > 64 ? substr($reportTitle, 0, 61) . "..." : $reportTitle) . " | " . $dateRange . " | ");
158 // add first page
159 $this->TCPDF->setPrintHeader(\false);
160 $this->TCPDF->AddPage(self::PORTRAIT);
161 $this->TCPDF->AddFont($this->reportFont, '', '', \false);
162 $this->TCPDF->SetFont($this->reportFont, $this->reportFontStyle, $this->reportSimpleFontSize);
163 $this->TCPDF->Bookmark(Piwik::translate('ScheduledReports_FrontPage'));
164 // logo
165 $customLogo = new CustomLogo();
166 $this->TCPDF->Image($customLogo->getLogoUrl(\true), $this->logoImagePosition[0], $this->logoImagePosition[1], 180 / ($factor = 2), 0, $type = '', $link = '', $align = '', $resize = \false, $dpi = 300);
167 $this->TCPDF->Ln(8);
168 // report title
169 $this->TCPDF->SetFont($this->reportFont, '', $this->reportHeaderFontSize + 5);
170 $this->TCPDF->SetTextColor($this->headerTextColor[0], $this->headerTextColor[1], $this->headerTextColor[2]);
171 $this->TCPDF->SetXY(10, 119);
172 $this->TCPDF->MultiCell(0, 40, $reportTitle, 0, 'L');
173 // date and period
174 $this->TCPDF->SetXY(10, 152);
175 $this->TCPDF->SetFont($this->reportFont, '', $this->reportHeaderFontSize);
176 $this->TCPDF->SetTextColor($this->reportTextColor[0], $this->reportTextColor[1], $this->reportTextColor[2]);
177 $this->TCPDF->MultiCell(0, 40, $dateRange, 0, 'L');
178 // description
179 $this->TCPDF->SetXY(10, 210);
180 $this->TCPDF->Write(1, $this->formatText($description));
181 // segment
182 if ($segment != null) {
183 $this->TCPDF->Ln();
184 $this->TCPDF->Ln();
185 $this->TCPDF->SetFont($this->reportFont, '', $this->reportHeaderFontSize - 2);
186 $this->TCPDF->SetTextColor($this->headerTextColor[0], $this->headerTextColor[1], $this->headerTextColor[2]);
187 $this->TCPDF->Write(1, $this->formatText(Piwik::translate('ScheduledReports_CustomVisitorSegment') . ' ' . $segment['name']));
188 }
189 $this->TCPDF->Ln(8);
190 $this->TCPDF->SetFont($this->reportFont, '', $this->reportHeaderFontSize);
191 $this->TCPDF->Ln();
192 }
193 /**
194 * Generate a header of page.
195 */
196 private function paintReportHeader()
197 {
198 $isAggregateReport = !empty($this->reportMetadata['dimension']);
199 // Graph-only report
200 static $graphOnlyReportCount = 0;
201 $graphOnlyReport = $isAggregateReport && $this->displayGraph && !$this->displayTable;
202 // Table-only report
203 $tableOnlyReport = $isAggregateReport && !$this->displayGraph && $this->displayTable;
204 $columnCount = count($this->reportColumns);
205 // Table-only 2-column report
206 static $tableOnly2ColumnReportCount = 0;
207 $tableOnly2ColumnReport = $tableOnlyReport && $columnCount == 2;
208 // Table-only report with more than 2 columns
209 static $tableOnlyManyColumnReportRowCount = 0;
210 $tableOnlyManyColumnReport = $tableOnlyReport && $columnCount > 3;
211 $reportHasData = $this->reportHasData();
212 $rowCount = $reportHasData ? $this->report->getRowsCount() + self::TABLE_HEADER_ROW_COUNT : self::NO_DATA_ROW_COUNT;
213 // Only a page break before if the current report has some data
214 if ($reportHasData && ($this->currentPage == 0 || $graphOnlyReport && $graphOnlyReportCount == 0 || $tableOnly2ColumnReport && $tableOnly2ColumnReportCount == 0 || $tableOnlyManyColumnReport && ($tableOnlyManyColumnReportRowCount == 0 || $tableOnlyManyColumnReportRowCount + $rowCount >= self::MAX_ROW_COUNT) || !$graphOnlyReport && !$tableOnlyReport)) {
215 $this->currentPage++;
216 $this->TCPDF->AddPage();
217 // Table-only reports with more than 2 columns are always landscape
218 if ($tableOnlyManyColumnReport) {
219 $tableOnlyManyColumnReportRowCount = 0;
220 $this->orientation = self::LANDSCAPE;
221 } else {
222 // Graph-only reports are always portrait
223 $this->orientation = $graphOnlyReport ? self::PORTRAIT : ($columnCount > $this->maxColumnCountPortraitOrientation ? self::LANDSCAPE : self::PORTRAIT);
224 }
225 $this->TCPDF->setPageOrientation($this->orientation, '', $this->bottomMargin);
226 }
227 $graphOnlyReportCount = $graphOnlyReport && $reportHasData ? ($graphOnlyReportCount + 1) % self::MAX_GRAPH_REPORTS : 0;
228 $tableOnly2ColumnReportCount = $tableOnly2ColumnReport && $reportHasData ? ($tableOnly2ColumnReportCount + 1) % self::MAX_2COL_TABLE_REPORTS : 0;
229 $tableOnlyManyColumnReportRowCount = $tableOnlyManyColumnReport ? $tableOnlyManyColumnReportRowCount + $rowCount : 0;
230 $title = $this->formatText($this->reportMetadata['name']);
231 $this->TCPDF->SetFont($this->reportFont, $this->reportFontStyle, $this->reportHeaderFontSize);
232 $this->TCPDF->SetTextColor($this->headerTextColor[0], $this->headerTextColor[1], $this->headerTextColor[2]);
233 $this->TCPDF->Bookmark($title);
234 $this->TCPDF->Cell(40, 15, $title);
235 $this->TCPDF->Ln();
236 $this->TCPDF->SetFont($this->reportFont, '', $this->reportSimpleFontSize);
237 $this->TCPDF->SetTextColor($this->reportTextColor[0], $this->reportTextColor[1], $this->reportTextColor[2]);
238 }
239 private function reportHasData()
240 {
241 return $this->report->getRowsCount() > 0;
242 }
243 private function setBorderColor()
244 {
245 $this->TCPDF->SetDrawColor($this->tableCellBorderColor[0], $this->tableCellBorderColor[1], $this->tableCellBorderColor[2]);
246 }
247 public function renderReport($processedReport)
248 {
249 $this->reportMetadata = $processedReport['metadata'];
250 $this->reportRowsMetadata = $processedReport['reportMetadata'];
251 $this->displayGraph = $processedReport['displayGraph'];
252 $this->evolutionGraph = $processedReport['evolutionGraph'];
253 $this->displayTable = $processedReport['displayTable'];
254 $this->segment = $processedReport['segment'];
255 list($this->report, $this->reportColumns) = self::processTableFormat($this->reportMetadata, $processedReport['reportData'], $processedReport['columns']);
256 $this->paintReportHeader();
257 if (!$this->reportHasData()) {
258 $this->paintMessage(Piwik::translate('CoreHome_ThereIsNoDataForThisReport'));
259 return;
260 }
261 if ($this->displayGraph) {
262 $this->paintGraph();
263 }
264 if ($this->displayGraph && $this->displayTable) {
265 $this->TCPDF->Ln(5);
266 }
267 if ($this->displayTable) {
268 $this->paintReportTableHeader();
269 $this->paintReportTable();
270 }
271 }
272 private function formatText($text)
273 {
274 return Common::unsanitizeInputValue($text);
275 }
276 private function paintReportTable()
277 {
278 //Color and font restoration
279 $this->TCPDF->SetFillColor($this->tableBackgroundColor[0], $this->tableBackgroundColor[1], $this->tableBackgroundColor[2]);
280 $this->TCPDF->SetTextColor($this->reportTextColor[0], $this->reportTextColor[1], $this->reportTextColor[2]);
281 $this->TCPDF->SetFont('');
282 $fill = \true;
283 $url = \false;
284 $leftSpacesBeforeLogo = str_repeat(' ', $this->leftSpacesBeforeLogo);
285 $logoWidth = $this->logoWidth;
286 $logoHeight = $this->logoHeight;
287 $rowsMetadata = $this->reportRowsMetadata->getRows();
288 // Draw a body of report table
289 foreach ($this->report->getRows() as $rowId => $row) {
290 $rowMetrics = $row->getColumns();
291 $rowMetadata = isset($rowsMetadata[$rowId]) ? $rowsMetadata[$rowId]->getColumns() : array();
292 if (isset($rowMetadata['url'])) {
293 $url = $rowMetadata['url'];
294 }
295 foreach ($this->reportColumns as $columnId => $columnName) {
296 // Label column
297 if ($columnId == 'label') {
298 $isLogoDisplayable = isset($rowMetadata['logo']);
299 $text = '';
300 $posX = $this->TCPDF->GetX();
301 $posY = $this->TCPDF->GetY();
302 if (isset($rowMetrics[$columnId])) {
303 $text = mb_substr($rowMetrics[$columnId], 0, $this->truncateAfter);
304 if ($isLogoDisplayable) {
305 $text = $leftSpacesBeforeLogo . $text;
306 }
307 }
308 $text = $this->formatText($text);
309 $this->TCPDF->Cell($this->labelCellWidth, $this->cellHeight, $text, 'LR', 0, 'L', $fill, $url);
310 if ($isLogoDisplayable) {
311 if (isset($rowMetadata['logoWidth'])) {
312 $logoWidth = $rowMetadata['logoWidth'];
313 }
314 if (isset($rowMetadata['logoHeight'])) {
315 $logoHeight = $rowMetadata['logoHeight'];
316 }
317 $restoreY = $this->TCPDF->getY();
318 $restoreX = $this->TCPDF->getX();
319 $this->TCPDF->SetY($posY);
320 $this->TCPDF->SetX($posX);
321 $topMargin = 1.3;
322 // Country flags are not very high, force a bigger top margin
323 if ($logoHeight < 16) {
324 $topMargin = 2;
325 }
326 $path = Filesystem::getPathToPiwikRoot() . "/" . $rowMetadata['logo'];
327 if (file_exists($path)) {
328 $this->TCPDF->Image($path, $posX + ($leftMargin = 2), $posY + $topMargin, $logoWidth / 4);
329 }
330 $this->TCPDF->SetXY($restoreX, $restoreY);
331 }
332 } else {
333 // metrics column
334 // No value means 0
335 if (empty($rowMetrics[$columnId])) {
336 $rowMetrics[$columnId] = 0;
337 }
338 $this->TCPDF->Cell($this->cellWidth, $this->cellHeight, NumberFormatter::getInstance()->format($rowMetrics[$columnId]), 'LR', 0, 'L', $fill);
339 }
340 }
341 $this->TCPDF->Ln();
342 // Top/Bottom grey border for all cells
343 $this->TCPDF->SetDrawColor($this->rowTopBottomBorder[0], $this->rowTopBottomBorder[1], $this->rowTopBottomBorder[2]);
344 $this->TCPDF->Cell($this->totalWidth, 0, '', 'T');
345 $this->setBorderColor();
346 $this->TCPDF->Ln(0.2);
347 $fill = !$fill;
348 }
349 }
350 private function paintGraph()
351 {
352 $imageGraph = parent::getStaticGraph($this->reportMetadata, $this->orientation == self::PORTRAIT ? self::IMAGE_GRAPH_WIDTH_PORTRAIT : self::IMAGE_GRAPH_WIDTH_LANDSCAPE, self::IMAGE_GRAPH_HEIGHT, $this->evolutionGraph, $this->segment);
353 $this->TCPDF->Image('@' . $imageGraph, $x = '', $y = '', $w = 0, $h = 0, $type = '', $link = '', $align = 'N', $resize = \false, $dpi = 72, $palign = '', $ismask = \false, $imgmask = \false, $order = 0, $fitbox = \false, $hidden = \false, $fitonpage = \true, $alt = \false, $altimgs = array());
354 unset($imageGraph);
355 }
356 /**
357 * Draw the table header (first row)
358 */
359 private function paintReportTableHeader()
360 {
361 $initPosX = 10;
362 // Get the longest column name
363 $longestColumnName = '';
364 foreach ($this->reportColumns as $columnName) {
365 if (strlen($columnName) > strlen($longestColumnName)) {
366 $longestColumnName = $columnName;
367 }
368 }
369 $columnsCount = count($this->reportColumns);
370 // Computes available column width
371 if ($this->orientation == self::PORTRAIT && $columnsCount <= 3) {
372 $totalWidth = $this->reportWidthPortrait * 2 / 3;
373 } elseif ($this->orientation == self::LANDSCAPE) {
374 $totalWidth = $this->reportWidthLandscape;
375 } else {
376 $totalWidth = $this->reportWidthPortrait;
377 }
378 $this->totalWidth = $totalWidth;
379 $this->labelCellWidth = max(round($this->totalWidth / $columnsCount), $this->minWidthLabelCell);
380 $this->cellWidth = round(($this->totalWidth - $this->labelCellWidth) / ($columnsCount - 1));
381 $this->totalWidth = $this->labelCellWidth + ($columnsCount - 1) * $this->cellWidth;
382 $this->TCPDF->SetFillColor($this->tableHeaderBackgroundColor[0], $this->tableHeaderBackgroundColor[1], $this->tableHeaderBackgroundColor[2]);
383 $this->TCPDF->SetTextColor($this->tableHeaderTextColor[0], $this->tableHeaderTextColor[1], $this->tableHeaderTextColor[2]);
384 $this->TCPDF->SetLineWidth(0.3);
385 $this->setBorderColor();
386 $this->TCPDF->SetFont($this->reportFont, $this->reportFontStyle);
387 $this->TCPDF->SetFillColor(255);
388 $this->TCPDF->SetTextColor($this->tableHeaderBackgroundColor[0], $this->tableHeaderBackgroundColor[1], $this->tableHeaderBackgroundColor[2]);
389 $this->TCPDF->SetDrawColor(255);
390 $posY = $this->TCPDF->GetY();
391 $this->TCPDF->MultiCell($this->cellWidth, $this->cellHeight, $longestColumnName, 1, 'C', \true);
392 $maxCellHeight = $this->TCPDF->GetY() - $posY;
393 $this->TCPDF->SetFillColor($this->tableHeaderBackgroundColor[0], $this->tableHeaderBackgroundColor[1], $this->tableHeaderBackgroundColor[2]);
394 $this->TCPDF->SetTextColor($this->tableHeaderTextColor[0], $this->tableHeaderTextColor[1], $this->tableHeaderTextColor[2]);
395 $this->TCPDF->SetDrawColor($this->tableCellBorderColor[0], $this->tableCellBorderColor[1], $this->tableCellBorderColor[2]);
396 $this->TCPDF->SetXY($initPosX, $posY);
397 $countColumns = 0;
398 $posX = $initPosX;
399 foreach ($this->reportColumns as $columnName) {
400 $columnName = $this->formatText($columnName);
401 //Label column
402 if ($countColumns == 0) {
403 $this->TCPDF->MultiCell($this->labelCellWidth, $maxCellHeight, $columnName, $border = 0, $align = 'L', \true);
404 $this->TCPDF->SetXY($posX + $this->labelCellWidth, $posY);
405 } else {
406 $this->TCPDF->MultiCell($this->cellWidth, $maxCellHeight, $columnName, $border = 0, $align = 'L', \true);
407 $this->TCPDF->SetXY($posX + $this->cellWidth, $posY);
408 }
409 $countColumns++;
410 $posX = $this->TCPDF->GetX();
411 }
412 $this->TCPDF->Ln();
413 $this->TCPDF->SetXY($initPosX, $posY + $maxCellHeight);
414 }
415 /**
416 * Prints a message
417 *
418 * @param string $message
419 * @return void
420 */
421 private function paintMessage($message)
422 {
423 $this->TCPDF->SetFont($this->reportFont, $this->reportFontStyle, $this->reportSimpleFontSize);
424 $this->TCPDF->SetTextColor($this->reportTextColor[0], $this->reportTextColor[1], $this->reportTextColor[2]);
425 $message = $this->formatText($message);
426 $this->TCPDF->Write("1em", $message);
427 $this->TCPDF->Ln();
428 }
429 /**
430 * Get report attachments, ex. graph images
431 *
432 * @param $report
433 * @param $processedReports
434 * @param $prettyDate
435 * @return array
436 */
437 public function getAttachments($report, $processedReports, $prettyDate)
438 {
439 return array();
440 }
441 }
442