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