1<?php
2/**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9namespace Piwik;
10
11use Exception;
12use Piwik\API\Request;
13use Piwik\Container\StaticContainer;
14use Piwik\DataTable\Row;
15use Piwik\DataTable\Simple;
16use Piwik\Plugins\ImageGraph\API;
17
18/**
19 * A Report Renderer produces user friendly renderings of any given Piwik report.
20 * All new Renderers must be copied in ReportRenderer and added to the $availableReportRenderers.
21 */
22abstract class ReportRenderer extends BaseFactory
23{
24    const DEFAULT_REPORT_FONT_FAMILY = 'dejavusans';
25    const REPORT_TEXT_COLOR = "13,13,13";
26    const REPORT_TITLE_TEXT_COLOR = "13,13,13";
27    const TABLE_HEADER_BG_COLOR = "255,255,255";
28    const TABLE_HEADER_TEXT_COLOR = "13,13,13";
29    const TABLE_HEADER_TEXT_TRANSFORM = "uppercase";
30    const TABLE_HEADER_TEXT_WEIGHT = "normal";
31    const TABLE_CELL_BORDER_COLOR = "217,217,217";
32    const TABLE_BG_COLOR = "242,242,242";
33
34    const HTML_FORMAT = 'html';
35    const PDF_FORMAT = 'pdf';
36    const CSV_FORMAT = 'csv';
37    const TSV_FORMAT = 'tsv';
38
39    protected $idSite = 'all';
40
41    protected $report;
42
43    private static $availableReportRenderers = array(
44        self::PDF_FORMAT,
45        self::HTML_FORMAT,
46        self::CSV_FORMAT,
47        self::TSV_FORMAT,
48    );
49
50    /**
51     * Sets the site id
52     *
53     * @param int $idSite
54     */
55    public function setIdSite($idSite)
56    {
57        $this->idSite = $idSite;
58    }
59
60    public function setReport($report)
61    {
62        $this->report = $report;
63    }
64
65    protected static function getClassNameFromClassId($rendererType)
66    {
67        return 'Piwik\ReportRenderer\\' . self::normalizeRendererType($rendererType);
68    }
69
70    protected static function getInvalidClassIdExceptionMessage($rendererType)
71    {
72        return Piwik::translate(
73            'General_ExceptionInvalidReportRendererFormat',
74            array(self::normalizeRendererType($rendererType), implode(', ', self::$availableReportRenderers))
75        );
76    }
77
78    protected static function normalizeRendererType($rendererType)
79    {
80        return ucfirst(strtolower($rendererType));
81    }
82
83    /**
84     * Initialize locale settings.
85     * If not called, locale settings defaults to 'en'
86     *
87     * @param string $locale
88     */
89    abstract public function setLocale($locale);
90
91    /**
92     * Save rendering to disk
93     *
94     * @param string $filename without path & without format extension
95     * @return string path of file
96     */
97    abstract public function sendToDisk($filename);
98
99    /**
100     * Send rendering to browser with a 'download file' prompt
101     *
102     * @param string $filename without path & without format extension
103     */
104    abstract public function sendToBrowserDownload($filename);
105
106    /**
107     * Output rendering to browser
108     *
109     * @param string $filename without path & without format extension
110     */
111    abstract public function sendToBrowserInline($filename);
112
113    /**
114     * Get rendered report
115     */
116    abstract public function getRenderedReport();
117
118    /**
119     * Generate the first page.
120     *
121     * @param string $reportTitle
122     * @param string $prettyDate formatted date
123     * @param string $description
124     * @param array $reportMetadata metadata for all reports
125     * @param array $segment segment applied to all reports
126     */
127    abstract public function renderFrontPage($reportTitle, $prettyDate, $description, $reportMetadata, $segment);
128
129    /**
130     * Render the provided report.
131     * Multiple calls to this method before calling outputRendering appends each report content.
132     *
133     * @param array $processedReport @see API::getProcessedReport()
134     */
135    abstract public function renderReport($processedReport);
136
137    /**
138     * Get report attachments, ex. graph images
139     *
140     * @param $report
141     * @param $processedReports
142     * @param $prettyDate
143     * @return array
144     */
145    abstract public function getAttachments($report, $processedReports, $prettyDate);
146
147    /**
148     * Append $extension to $filename
149     *
150     * @static
151     * @param  string $filename
152     * @param  string $extension
153     * @return string  filename with extension
154     */
155    protected static function makeFilenameWithExtension($filename, $extension)
156    {
157        // the filename can be used in HTTP headers, remove new lines to prevent HTTP header injection
158        $filename = str_replace(array("\n", "\t"), " ", $filename);
159
160        return $filename . "." . $extension;
161    }
162
163    /**
164     * Return $filename with temp directory and delete file
165     *
166     * @static
167     * @param  $filename
168     * @return string path of file in temp directory
169     */
170    protected static function getOutputPath($filename)
171    {
172        $outputFilename = StaticContainer::get('path.tmp') . '/assets/' . $filename;
173
174        @chmod($outputFilename, 0600);
175
176        if(file_exists($outputFilename)){
177            @unlink($outputFilename);
178        }
179
180        return $outputFilename;
181    }
182
183    protected static function writeFile($filename, $extension, $content)
184    {
185        $filename = self::makeFilenameWithExtension($filename, $extension);
186        $outputFilename = self::getOutputPath($filename);
187
188        $bytesWritten = file_put_contents($outputFilename, $content);
189        if ($bytesWritten === false) {
190            throw new Exception("ReportRenderer: Could not write to file '" . $outputFilename . "'.");
191        }
192
193        return $outputFilename;
194    }
195
196    protected static function sendToBrowser($filename, $extension, $contentType, $content)
197    {
198        $filename = ReportRenderer::makeFilenameWithExtension($filename, $extension);
199
200        ProxyHttp::overrideCacheControlHeaders();
201        Common::sendHeader('Content-Description: File Transfer');
202        Common::sendHeader('Content-Type: ' . $contentType);
203        Common::sendHeader('Content-Disposition: attachment; filename="' . str_replace('"', '\'', basename($filename)) . '";');
204        Common::sendHeader('Content-Length: ' . strlen($content));
205
206        echo $content;
207    }
208
209    protected static function inlineToBrowser($contentType, $content)
210    {
211        Common::sendHeader('Content-Type: ' . $contentType);
212        echo $content;
213    }
214
215    /**
216     * Convert a dimension-less report to a multi-row two-column data table
217     *
218     * @static
219     * @param  $reportMetadata array
220     * @param  $report DataTable
221     * @param  $reportColumns array
222     * @return array DataTable $report & array $columns
223     */
224    protected static function processTableFormat($reportMetadata, $report, $reportColumns)
225    {
226        $finalReport = $report;
227        if (empty($reportMetadata['dimension'])) {
228            $simpleReportMetrics = $report->getFirstRow();
229            if ($simpleReportMetrics) {
230                $finalReport = new Simple();
231                foreach ($simpleReportMetrics->getColumns() as $metricId => $metric) {
232                    $newRow = new Row();
233                    $newRow->addColumn("label", $reportColumns[$metricId]);
234                    $newRow->addColumn("value", $metric);
235                    $finalReport->addRow($newRow);
236                }
237            }
238
239            $reportColumns = array(
240                'label' => Piwik::translate('General_Name'),
241                'value' => Piwik::translate('General_Value'),
242            );
243        }
244
245        return array(
246            $finalReport,
247            $reportColumns,
248        );
249    }
250
251    public static function getStaticGraph($reportMetadata, $width, $height, $evolution, $segment)
252    {
253        $imageGraphUrl = $reportMetadata['imageGraphUrl'];
254
255        if ($evolution && !empty($reportMetadata['imageGraphEvolutionUrl'])) {
256            $imageGraphUrl = $reportMetadata['imageGraphEvolutionUrl'];
257        }
258
259        $requestGraph = $imageGraphUrl .
260            '&outputType=' . API::GRAPH_OUTPUT_PHP .
261            '&format=original&serialize=0' .
262            '&filter_truncate=' .
263            '&width=' . $width .
264            '&height=' . $height .
265            ($segment != null ? '&segment=' . urlencode($segment['definition']) : '');
266
267        $request = new Request($requestGraph);
268
269        try {
270            $imageGraph = $request->process();
271
272            // Get image data as string
273            ob_start();
274            imagepng($imageGraph);
275            $imageGraphData = ob_get_contents();
276            ob_end_clean();
277            imagedestroy($imageGraph);
278
279            return $imageGraphData;
280        } catch (Exception $e) {
281            throw new Exception("ImageGraph API returned an error: " . $e->getMessage() . "\n");
282        }
283    }
284}
285