matomo-org / matomo

Empowering People Ethically with the leading open source alternative to Google Analytics that gives you full control over your data. Matomo lets you easily collect data from websites & apps and visualise this data and extract insights. Privacy is built-in. Liberating Web Analytics. Star us on Github? +1. And we love Pull Requests!
https://matomo.org/
GNU General Public License v3.0
19.64k stars 2.62k forks source link

Plugin to provide PDF export of data #5491

Closed mattab closed 12 years ago

mattab commented 16 years ago

Users need to share Piwik reports with colleagues, customers or externals. PDF is the standard way of exchanging rich data, with graphs, colors, consistent design.

Proposed features set of Piwik PDF export

For the first release of PDF export in Piwik, I think the following features would be important to have:

Other features probably for later releases:

Features currently not considered

Here are some thoughts about the implementation. Please discuss before implementing, there might be better solutions.

API to render PDF

Rendering a PDF could then be done by using following call:

module=API&
method=PDF.getReport&
report=$REPORT_ID&
idSite=$WEBSITE_ID&
token_auth=$TOKEN&
date=...&period=...

In V1, when customReports are not implemented, we would have a function PDF.getFullWebsiteReport which would return the PDF containing all reports for a given website.

There could also be a PDF.getWebsitesDashboard which would return a simple PDF containing main metrics for all websites visible to the user.

Flow of a request to generate a PDF containing all reports

When called, the PDF API function would then:

Basic skeleton for the API method PDF.getReport

function getReport($report, $idSite, $date, $period)
{

 // set the default View to PDF Piwik_View::setDefaultView(new Piwik_View_PDF());
 // define an array of Controllers/methods to call to render in this PDF, 
 // eg. array( array( 'Goals', 'index'), array('UserSettings', 'getBrowser') )
 // loop over all the array of methods to render
           // here we could add a new page, a pdf page title for the next report, etc.
            // for each controller method, call it, for example
            $content = Piwik_FrontController::getInstance()->fetchDispatch( 'Goals', 'index');
            // this will call the index() on Goals/Controller.php
            // which will, at the end, render the view eg. $view->getView()->render()
           // this will result in Piwik_View_PDF->render() call which adds the right data to the PDF file being built.

 // once all reports have been added to PDF, return PDF to the user
}

A new View type

Currently in plugins Controllers, views are created with:

$view = new Piwik_View('Goals/templates/single_goal.tpl');

The view object encapsulates the Smarty implementation. Variables are set to the view $view->name = $goalDefinition['name']; and the view is rendered at the end of the controller method: echo $view->render();

In order to add PDF export with minimal effort, PDF could be a new View class, implementing the iView interface.

Currently there is core/View.php which should probably be moved to core/View/Smarty.php

there is some generic code in render() which could probably be left in the new core/View.php parent object.

We could have Piwik_View_Smarty extends Piwik_View then we would build Piwik_View_PDF extends Piwik_View

When building a PDF report, each controller's method would be called, and the data would be added to the PDF report hold in this object. The report builder API would then output this aggregated PDF to the user.

Adding renderer

Piwik_View_PDF could look at the template name passed in the view constructor, and based on this find out if it knows how to render this template. For example, calling the Goals.goalReport method (in plugins/Goals/Controller.php), the View is using the template Goals/templates/single_goal.tpl - if the view does know how to render this (given the variables are set properly like on the smarty template), then it can render it. If not, an error can be output. The PDF view would have logic that says: "Goals/templates/single_goal" must be rendered this way. Most templates are generic (datatable, etc.) so most of the work to render the reports would be done only once and automatically work for most reports.

Rendering text, tables in the PDF

The library might support automatic conversion of HTML into the PDF, but most likely this doesn't work very well. We will most likely have to write a basic PDF renderer for the datatable (equivalent of datatable.tpl).

Custom reports

By default, we can include all tables in the PDF. But reports are useful only if they contain the subset of information a given user is interested in. We could have a simple UI, listing all available reports, with a click and add to report feature. This would be internally linked to calls to controller methods. The layout could be saved as JSON in the reports table.

UI integration

PDF reports do not apply on per report basis, like Bar graphs or Pie charts apply. There could be a link to generate a PDF report for the current website in the header.

Contributions

Interested by this feature? Let us know in the comments.

anonymous-matomo-user commented 14 years ago

Attachment: Admin screen (not complete yet) admin_screen.png

anonymous-matomo-user commented 14 years ago

Attachment: Position of plugin entry for users menu.png

mattab commented 14 years ago

Attachment: V 0.1 Pdfexport.zip

anonymous-matomo-user commented 14 years ago

Attachment: Pdfexport.2.zip

anonymous-matomo-user commented 14 years ago

Attachment: Plugin + patch PluginPDF_V0.3+patch.zip

mattab commented 14 years ago

Attachment: empty report with empty columns, and with a large logo (logo can be aligned on left and resized to fit on page) buggy report.pdf

anonymous-matomo-user commented 14 years ago

Attachment: Pdfexport+patch_V0.4.zip

anonymous-matomo-user commented 15 years ago

I'd love to see this happen

anonymous-matomo-user commented 15 years ago

Why not render the graphs in PDF? The TCPDF PHP library already has a PieSector ( http://www.tecnick.com/pagefiles/tcpdf/doc/com-tecnick-tcpdf/TCPDF.html#methodPieSector ) function to render a "Pie Piece". This would generate a vector graph which only need a few bytes in the PDF and can be freely scaled without getting blurry. I'm sure there's also some bargraph code somewhere ... if not, it's easy to create one.

anonymous-matomo-user commented 14 years ago

Started to work on this plugin. Will be have a beta version for testing soon. Today I want to tell you what we implemented so far. Also I'll screenshots.

First we add in the admin interface a new tab: PDF Export Settings Here you can set some text blocks which will be used as comments in the PDF export. It takes all data from the widget preview view.

In the beta release we also want to have a dropdown where you can choose a template for the PDF report (own logo, etc.)

Also we gave you the possibility to save the reports on you server in a specified directory.

In the front we add a new tab like "goals" or "actions". The rest is similar to the goal plugin.

You can add a new report and also see which reports exists for this idSite. This feature is usefull if you want to generate different reports for different user groups (e.g. management report or design/graphical report).

Here you can choose start and end date and also if you want to have diagrams in the report.

At the moment you can only download the PDF by clicking on a button.

For PDF generation we use fpdf library, because we use this library before (over the API) and it was easily to install in the plugin.

If you have some comments on the screens please let me know. More screens will follow until we release the beta version.

mattab commented 14 years ago

Christian, thanks for updating us.

It sounds like you are making great progress. Let us know if you have questions, missing hooks in the code, etc. that would block your development. We're really looking forward to seeing this PDF export :)

anonymous-matomo-user commented 14 years ago

Hello,

This plugin seems to be very interresting. I must developp a similary plugin which enables people to export data in PDF format. I would like to know the project progress and if I can help you on your work.

Could you please contact me by mail at: jeremy.lavaux@inist.fr Thanks

mattab commented 14 years ago

Feedback on V0.1 submitted by Christian.

First of all, very good start :) Here is a review of the screens/UI and the code.

UI/product feedback

Code feedback

Clarification on 'generating PDF' screen


$user = Piwik_UsersManager_API::getInstance()->getUser(Piwik::getCurrentUserLogin());
$userEmail = $user['email']; 

I would recommend if possible you work on these features first without adding new ones, and we can try to have a beta version that we can integrate in core. After this we could work on other new features (sending reports by email, adding graphs, etc.). Thanks!

mattab commented 14 years ago

(In [2312]) Refs #5491

anonymous-matomo-user commented 14 years ago

Attached the beta version for PDF export with the comments by Matt. Waiting for a new code review. Thank to Jeremy and Alex for the work done on this plugin to the beta status.

mattab commented 14 years ago

In the submitted version, I wasn't able to generate a PDF with reports (list of reports didn't display). I'll still do a code review:

    $data[$k]['filename']    = "./plugins/Pdfexport/reports/".$template['date_from']."-".$template['date_period']."_".$template['site_id'].".pdf";

                if (file_exists('./plugins/Pdfexport/reports/'.$template['date_from'].'-'.$template['date_period'].'_'.$template['site_id'].".pdf"))

Let me know when you need a new code review, good progress, but some cleaning work left before we can integrate. Thanks!

mattab commented 14 years ago

Code review

Core patch

PDF plugin feedback

User Interface

code review


                    if (isset($standardColumnNameToTranslation[$key]))
                        $elementsArray[$key]   = Piwik_Translate($standardColumnNameToTranslation[$key]);
                    else
                        $elementsArray[$key] = $key;

you can write


                        $elementsArray[$key] = $key;
                    if (isset($standardColumnNameToTranslation[$key]))
                        $elementsArray[$key]   = Piwik_Translate($standardColumnNameToTranslation[$key]);

            if ($this->date == 'today')
                $this->date = date('Y-m-d');
            if ($this->date == 'yesterday')

use Piwik_Date::factory($this->date)->get('Y-m-d') which will handle all cases


        $url = "method=".'Pdfexport.getReport';
        $url .= "&idSite=".$idSite."&period=".$period."&date=".$date_from."&id=".$insertResult->getAdapter()->lastInsertId();
        $url .= "&format=php";
        $request = new Piwik_API_Request($url);
        $request->process();

Instead you can just do $this->getReport($idSite, .... ) which is the same

 if (file_exists($this->reportFilePathGenerate($template)))
            unlink($this->reportFilePathGenerate($template));

Please check all the code and make all is refactored (very important for maintenance)


            switch ($templateToSend[0]['date_period'])
            {
                case "day":
                    $date_To = $piwik_date->addPeriod(1,'day')->toString();
                    break;
                case "week":
                    $date_To = $piwik_date->addPeriod(1,'week')->toString();
                    break;
                case "month":
                    $date_To = $piwik_date->addPeriod(1,'month')->toString();
                    break;
                case "year":
                    $date_To = $piwik_date->addPeriod(1,'year')->toString();
                    break;
                default:
                    break;
            }

should simply be

$period = $templateToSend[0]['date_period'];
$dateTo = $piwik_date->addPeriod(1,$period)->toString();
    switch ($piwik_date->getLocalized('%longDay%'))
            {
                case Piwik_Translate('General_LongDay_1'):
                    $date_from = $piwik_date->toString();
                    break;
                case Piwik_Translate('General_LongDay_2'):
                    $date_from = $piwik_date->subDay(1)->toString();
                    break;
                case Piwik_Translate('General_LongDay_3'):
                    $date_from = $piwik_date->subDay(2)->toString();
                    break;
                case Piwik_Translate('General_LongDay_4'):
                    $date_from = $piwik_date->subDay(3)->toString();
                    break;  
                case Piwik_Translate('General_LongDay_5'):
                    $date_from = $piwik_date->subDay(4)->toString();
                    break;
                case Piwik_Translate('General_LongDay_6'):
                    $date_from = $piwik_date->subDay(5)->toString();
                    break;
                case Piwik_Translate('General_LongDay_7'):
                    $date_from = $piwik_date->subDay(6)->toString();
                    break;
                default:

                    break;
            }

you could write something like

$dateFrom = $date->subDay($date->get('N'))->toString(); (see http://fr.php.net/manual/en/function.date.php)

as said, I haven't tested functionnality yet as I'm waiting for patch. Good luck :) let me know if you need help

mattab commented 14 years ago

new files: http://dl.free.fr/d98xMeThE

Good work on the review, the code is now getting much better!! I hope we can reach a good working version soon. Generating pdf was not working for me (see attached pdf) but I still reviewed code and UIs

Could you please attach a PDF with all reports that are possible to generate, and some data, so I can see how it looks? thanks

Core Code review

            if($_FILES['userfile']['size'] < MAX_SIZE)
            {
                [.....] very long
            }
            else
            {
                throw new Exception('Size error: a maximum of 100ko is allowed');
            }

write the more simple


            if($_FILES['userfile']['size'] > MAX_SIZE)
            {
                throw new Exception('Size error: a maximum of 100ko is allowed');
            }
                        [....] very long

makes code much more readable. i saw it in other codes like sendTemplate() and sendEmail() etc.

UI

Code review

            $piwikHost = @$_SERVER['HTTP_HOST'];
            if(strlen($piwikHost) == 0)
            {
                $piwikHost = 'piwik.org';
            }

Schemas

To meet requirements from spec, here are some changes to make to schema. Some other templates/php changes will follow in some cases...

Finally, can you please remove all TCPDF files that are not directly used by Piwik? TCPDF is a great library but is very heavy. Can you please remove fonts, images, docs, etc. that are not used? (you can leave README and LICENSE please). We are trying hard not to increase the piwik package size and are careful about adding only useful files :)

Looking forward to next release and hopefully I can generate a PDF next time :) we're getting closed to commit to trunk for sure, good stuff!

mattab commented 14 years ago

Actually, the PDF now load, but I had to install a Japanese font. Why? without this font (of course I didn't take a screenshot of the popup...) I couldn't display in acrobat reader, strange..

mattab commented 14 years ago

OK I was able to generate the PDF

Looks good :) few suggestions

Thanks!

anonymous-matomo-user commented 14 years ago

Hi,

You can find above our last version.

Things which are not in the V0.4:

Our choices:

logo upload, when there is a problem, just fails with a message on white page... it should instead be displayed with the standard error message on the screen (catch the exception in controller and set error message)

> For the moment it's quite difficult to manage the problem in Ajax so the error will be displayed in a white page.

_Remove updateTemplate not used, or if time allows, makes the Template listing have a Edit button, like Goals, and reopen the Add template form pre-selected with the current template to edit... The edit should only update the name, and list of reports to display, just like the Add template screen. _

> We made a remove of updateTemplate. A later version could take care of this modification.

Known issues:

Due to Ajax request, sometimes the page does not refresh and block on "Loading Data". Just Click again on "Pdf export" in the top bar to refresh.

mattab commented 14 years ago

(In [2662]) Fixes #1509 - Thanks Jeremy for initial code! Refs #5491

Note: I haven't tested SMTP myself. will require beta tests

mattab commented 14 years ago

(In [2663]) Refs #1485

peterbo commented 14 years ago

Great plugin so far!

My Ideas for discussion:

Footer-MetaData imaginable:

the usecase would be that someone compares two or more reports in printed versions. Without these meta-data you would be lost in loose sites.

Are seo-metrics already included in the reports? e.g. google indexed sites, backlinks, pagerank, etc.?

mattab commented 14 years ago

(In [2697]) Refs #5491

mattab commented 14 years ago

(In [2700]) Refs #5491 Fixing small ui issues

robocoder commented 14 years ago

(In [2706]) refs #5491

mattab commented 14 years ago

(In [2737]) Refs #5491

mattab commented 14 years ago

(In [2747]) Refs #5491 Anonymous users can't create/view/schedule PDF.

mattab commented 14 years ago

(In [2749]) Refs #5491 Removing unused fonts from libs trying to keep release size at minimum

anonymous-matomo-user commented 14 years ago

I created a new report containing the info I need. Afterwards I press "download" in order to see the result an get 3 big messages:

1: Warning: imagepng() href='http://de.php.net/function.imagepng'>function.imagepng</a>: open_basedir restriction in effect. File(/tmp/jpg_3S5g7f) is not within the allowed path(s): (/users/jmp/temp:/users/jmp/www) in /users/jmp/www/piwik/libs/tcpdf/tcpdf.php on line 6701

2: Warning: imagepng() href='http://de.php.net/function.imagepng'>function.imagepng</a>: Invalid filename in /users/jmp/www/piwik/libs/tcpdf/tcpdf.php on line 6701

3: Warning: fopen() href='http://de.php.net/function.fopen'>function.fopen</a>: open_basedir restriction in effect. File(/tmp/jpg_3S5g7f) is not within the allowed path(s): (/users/jmp/temp:/users/jmp/www) in /users/jmp/www/piwik/libs/tcpdf/tcpdf.php on line 6765

4: Warning: fopen(/tmp/jpg_3S5g7f) href='http://de.php.net/function.fopen'>function.fopen</a>: failed to open stream: Operation not permitted in /users/jmp/www/piwik/libs/tcpdf/tcpdf.php on line 6765

TCPDF ERROR: Can't open image file: /tmp/jpg_3S5g7f

Each message has its own backtrace

Is it a failure or is it related to my shared webspace settings?

anonymous-matomo-user commented 14 years ago

@matt: I tried your patch but there was no difference. Seems also that this randomly generated jpg_xxx-file does not exist. I think that the issue is related to my settings which cannot be changed. Thanks.

anonymous-matomo-user commented 14 years ago

please add those 2 folders:

libs/tcpdf/cache libs/tcpdf/images

Afterwards pdf-files can be generated. Thanks!

anonymous-matomo-user commented 13 years ago

PDFReport Cache Folders are not in Piwik 1.0 installation package

libs/tcpdf/cache libs/tcpdf/images

please put those folders in package.

greetings

robocoder commented 13 years ago

Re: last two comments. See #1656

This ticket is closed.

julienmoumne commented 12 years ago

(In [5415]) fixes #2706

julienmoumne commented 12 years ago

(In [5582]) * fixes #2706, #2828, #2704, refs #1721, #2637, #2711, #2318, #71 : horizontal static graph implemented

julienmoumne commented 12 years ago

(In [6478]) fixes #2708

refs #5491

julienmoumne commented 12 years ago

(In [6849]) refs #3323 #3088 #2708 #71 #2318