mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-12 12:33:26 +00:00
PHRAS-2189 report v2 master (#2742)
move code to services added "download" report change services to factory added excel lib added prod/report routes (download) cleanup api routes add : allow anonymized (user, fonction, societe... are "-") removed : xls support (memory eating lib) in favor of xlsx add : report download only on "document" and "preview" subdef classes cs : report factory add : restored "site" filter (see todos in src) remove debug, cs todo : doc
This commit is contained in:
@@ -119,7 +119,8 @@
|
||||
"alchemy/worker-bundle": "^0.1.6",
|
||||
"alchemy/queue-bundle": "^0.1.5",
|
||||
"google/recaptcha": "^1.1",
|
||||
"facebook/graph-sdk": "^5.6"
|
||||
"facebook/graph-sdk": "^5.6",
|
||||
"box/spout": "^2.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"mikey179/vfsStream": "~1.5",
|
||||
|
70
composer.lock
generated
70
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "27b7c5232802fb93dcf5dc79141df96f",
|
||||
"content-hash": "253990d4c81f9ae5f78c6c1221d6cf29",
|
||||
"packages": [
|
||||
{
|
||||
"name": "alchemy-fr/tcpdf-clone",
|
||||
@@ -1024,6 +1024,74 @@
|
||||
],
|
||||
"time": "2015-09-28T16:26:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "box/spout",
|
||||
"version": "v2.7.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/box/spout.git",
|
||||
"reference": "3681a3421a868ab9a65da156c554f756541f452b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/box/spout/zipball/3681a3421a868ab9a65da156c554f756541f452b",
|
||||
"reference": "3681a3421a868ab9a65da156c554f756541f452b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-xmlreader": "*",
|
||||
"ext-zip": "*",
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-iconv": "To handle non UTF-8 CSV files (if \"php-intl\" is not already installed or is too limited)",
|
||||
"ext-intl": "To handle non UTF-8 CSV files (if \"iconv\" is not already installed)"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.8.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Box\\Spout\\": "src/Spout"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Adrien Loison",
|
||||
"email": "adrien@box.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP Library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way",
|
||||
"homepage": "https://www.github.com/box/spout",
|
||||
"keywords": [
|
||||
"OOXML",
|
||||
"csv",
|
||||
"excel",
|
||||
"memory",
|
||||
"odf",
|
||||
"ods",
|
||||
"office",
|
||||
"open",
|
||||
"php",
|
||||
"read",
|
||||
"scale",
|
||||
"spreadsheet",
|
||||
"stream",
|
||||
"write",
|
||||
"xlsx"
|
||||
],
|
||||
"time": "2017-09-25T19:44:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "cocur/slugify",
|
||||
"version": "v2.3",
|
||||
|
@@ -24,9 +24,11 @@ use Alchemy\Phrasea\Core\Event\Subscriber\ApiExceptionHandlerSubscriber;
|
||||
use Alchemy\Phrasea\Core\Event\Subscriber\ApiOauth2ErrorsSubscriber;
|
||||
use Alchemy\Phrasea\Core\PhraseaEvents;
|
||||
use Alchemy\Phrasea\Core\Provider\JsonSchemaServiceProvider;
|
||||
use Alchemy\Phrasea\Report\ControllerProvider\ApiReportControllerProvider;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
|
||||
class ApiApplicationLoader extends BaseApplicationLoader
|
||||
{
|
||||
protected function doPrePluginServiceRegistration(Application $app)
|
||||
@@ -34,6 +36,7 @@ class ApiApplicationLoader extends BaseApplicationLoader
|
||||
$app->register(new OAuth2());
|
||||
$app->register(new V1());
|
||||
$app->register(new V2());
|
||||
$app->register(new ApiReportControllerProvider());
|
||||
$app->register(new JsonSchemaServiceProvider());
|
||||
}
|
||||
|
||||
@@ -132,6 +135,7 @@ class ApiApplicationLoader extends BaseApplicationLoader
|
||||
$app->mount('/datafiles/', new Datafiles());
|
||||
$app->mount('/api/v1', new V1());
|
||||
$app->mount('/api/v2', new V2());
|
||||
$app->mount('/api/report', new ApiReportControllerProvider());
|
||||
$app->mount('/permalink/', new Permalink());
|
||||
$app->mount($app['controller.media_accessor.route_prefix'], new MediaAccessor());
|
||||
$app->mount('/include/minify/', new Minifier());
|
||||
|
@@ -5,9 +5,11 @@ namespace Alchemy\Phrasea\Application;
|
||||
use Alchemy\EmbedProvider\EmbedServiceProvider;
|
||||
use Alchemy\Phrasea\Application;
|
||||
use Alchemy\Phrasea\ControllerProvider as Providers;
|
||||
use Alchemy\Phrasea\Report\ControllerProvider\ProdReportControllerProvider;
|
||||
use Assert\Assertion;
|
||||
use Silex\ControllerProviderInterface;
|
||||
|
||||
|
||||
class RouteLoader
|
||||
{
|
||||
|
||||
@@ -53,6 +55,7 @@ class RouteLoader
|
||||
'/prod/records/edit' => Providers\Prod\Edit::class,
|
||||
'/prod/records/movecollection' => Providers\Prod\MoveCollection::class,
|
||||
'/prod/records/property' => Providers\Prod\Property::class,
|
||||
'/prod/report/' => ProdReportControllerProvider::class,
|
||||
'/prod/share/' => Providers\Prod\Share::class,
|
||||
'/prod/story' => Providers\Prod\Story::class,
|
||||
'/prod/subdefs' => Providers\Prod\Subdefs::class,
|
||||
|
@@ -77,6 +77,7 @@ class ControllerProviderServiceProvider implements ServiceProviderInterface
|
||||
Prod\Push::class => [],
|
||||
Prod\Query::class => [],
|
||||
Prod\Record::class => [],
|
||||
\Alchemy\Phrasea\Report\ControllerProvider\ProdReportControllerProvider::class => [],
|
||||
Prod\Root::class => [],
|
||||
Prod\Share::class => [],
|
||||
Prod\Story::class => [],
|
||||
|
86
lib/Alchemy/Phrasea/Out/Module/Excel.php
Normal file
86
lib/Alchemy/Phrasea/Out/Module/Excel.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Out\Module;
|
||||
|
||||
use Box\Spout\Writer;
|
||||
use Box\Spout\Writer\WriterFactory;
|
||||
use Box\Spout\Common\Type;
|
||||
|
||||
|
||||
class Excel
|
||||
{
|
||||
const FORMAT_CSV = 'format_csv';
|
||||
const FORMAT_ODS = 'format_ods';
|
||||
const FORMAT_XLSX = 'format_xlsx';
|
||||
|
||||
private $format;
|
||||
|
||||
/** @var \Box\Spout\Writer\WriterInterface */
|
||||
private $writer;
|
||||
|
||||
|
||||
public function __construct($format, $filename)
|
||||
{
|
||||
$this->format = $format;
|
||||
|
||||
switch($format) {
|
||||
case self::FORMAT_CSV:
|
||||
/** @var Writer\CSV\Writer $writer */
|
||||
$writer = WriterFactory::create(Type::CSV);
|
||||
$writer->setFieldDelimiter(';')
|
||||
->setShouldAddBOM(false);
|
||||
break;
|
||||
case self::FORMAT_ODS:
|
||||
/** @var Writer\ODS\Writer $writer */
|
||||
$writer = WriterFactory::create(Type::ODS);
|
||||
break;
|
||||
case self::FORMAT_XLSX:
|
||||
/** @var Writer\XLSX\Writer $writer */
|
||||
$writer = WriterFactory::create(Type::XLSX);
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf("format \"%s\" is not handled by Spout"));
|
||||
break;
|
||||
}
|
||||
|
||||
$writer->openToBrowser($filename);
|
||||
$this->writer = $writer;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->writer->close();
|
||||
}
|
||||
|
||||
public function getActiveSheet()
|
||||
{
|
||||
if($this->format == self::FORMAT_CSV) {
|
||||
return "_unique_sheet_";
|
||||
}
|
||||
/** @var Writer\XLSX\Writer $w */
|
||||
$w = $this->writer;
|
||||
$sheetIndex = $w->getCurrentSheet()->getIndex();
|
||||
|
||||
return $sheetIndex;
|
||||
}
|
||||
|
||||
public function addRow($row)
|
||||
{
|
||||
$this->writer->addRow($row);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$this->writer->close();
|
||||
}
|
||||
|
||||
}
|
88
lib/Alchemy/Phrasea/Out/Module/Excel_bad.php
Normal file
88
lib/Alchemy/Phrasea/Out/Module/Excel_bad.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
namespace Alchemy\Phrasea\Out\Module;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
||||
class Excel
|
||||
{
|
||||
const FORMAT_CSV = 'format_csv';
|
||||
const FORMAT_XLS = 'format_xls';
|
||||
const FORMAT_XLSX = 'format_xlsx';
|
||||
|
||||
private $spreadsheet;
|
||||
|
||||
/** @var int[] * /
|
||||
private $currentRowBySheet;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->currentRowBySheet = [];
|
||||
$this->spreadsheet = new Spreadsheet();
|
||||
}
|
||||
|
||||
public function getActiveSheet()
|
||||
{
|
||||
$sheetIndex = $this->spreadsheet->getActiveSheetIndex();
|
||||
if(!array_key_exists($sheetIndex, $this->currentRowBySheet)) {
|
||||
$this->currentRowBySheet[$sheetIndex] = 1;
|
||||
}
|
||||
|
||||
return $this->spreadsheet->getActiveSheet();
|
||||
}
|
||||
|
||||
public function addRow($row)
|
||||
{
|
||||
$sheet = $this->getActiveSheet();
|
||||
$sheetIndex = $this->spreadsheet->getActiveSheetIndex();
|
||||
/** @var int $r * /
|
||||
$r = $this->currentRowBySheet[$sheetIndex];
|
||||
$c = 1;
|
||||
foreach($row as $v) {
|
||||
$sheet->setCellValueByColumnAndRow($c++, $r, $v);
|
||||
}
|
||||
$this->currentRowBySheet[$sheetIndex] = $r+1;
|
||||
}
|
||||
|
||||
public function fill()
|
||||
{
|
||||
$sheet = $this->getActiveSheet();
|
||||
$sheet->setCellValue('A1', 'Hello World !');
|
||||
}
|
||||
|
||||
public function render($format)
|
||||
{
|
||||
switch($format) {
|
||||
case self::FORMAT_XLS:
|
||||
header('Content-Type: application/vnd.ms-excel');
|
||||
$writer = IOFactory::createWriter($this->spreadsheet, 'Xls');
|
||||
break;
|
||||
case self::FORMAT_XLSX:
|
||||
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
$writer = IOFactory::createWriter($this->spreadsheet, 'Xlsx');
|
||||
break;
|
||||
}
|
||||
header('Content-Disposition: attachment;filename="myfile.xls"');
|
||||
header('Cache-Control: max-age=0');
|
||||
|
||||
$writer = IOFactory::createWriter($this->spreadsheet, 'Xls');
|
||||
|
||||
$writer->save('php://output');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
*/
|
145
lib/Alchemy/Phrasea/Report/Controller/ApiReportController.php
Normal file
145
lib/Alchemy/Phrasea/Report/Controller/ApiReportController.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Report\Controller;
|
||||
|
||||
use Alchemy\Phrasea\Application\Helper\JsonBodyAware;
|
||||
use Alchemy\Phrasea\Controller\Api\Result;
|
||||
use Alchemy\Phrasea\Report\ReportConnections;
|
||||
use Alchemy\Phrasea\Report\ReportDownloads;
|
||||
use Alchemy\Phrasea\Report\ReportFactory;
|
||||
use Alchemy\Phrasea\Report\ReportRecords;
|
||||
use Alchemy\Phrasea\Report\ReportService;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
|
||||
class ApiReportController
|
||||
{
|
||||
use JsonBodyAware;
|
||||
|
||||
private $reportFactory;
|
||||
private $reportService;
|
||||
private $anonymousReport;
|
||||
private $acl;
|
||||
|
||||
|
||||
/**
|
||||
* @param ReportFactory $reportFactory
|
||||
* @param ReportService $reportService
|
||||
* @param Bool $anonymousReport
|
||||
* @param \ACL $acl
|
||||
*/
|
||||
public function __construct(ReportFactory $reportFactory, ReportService $reportService, $anonymousReport, \ACL $acl)
|
||||
{
|
||||
$this->reportFactory = $reportFactory;
|
||||
$this->reportService = $reportService;
|
||||
$this->anonymousReport = $anonymousReport;
|
||||
$this->acl = $acl;
|
||||
}
|
||||
|
||||
/**
|
||||
* route api/report
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function rootAction(Request $request)
|
||||
{
|
||||
$ret = [
|
||||
'granted' => $this->reportService->getGranted()
|
||||
];
|
||||
|
||||
$result = Result::create($request, $ret);
|
||||
|
||||
return $result->createResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* route api/report/connections
|
||||
*
|
||||
* @param Request $request
|
||||
* @param $sbasId
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function connectionsAction(Request $request, $sbasId)
|
||||
{
|
||||
/** @var ReportConnections $report */
|
||||
$report = $this->reportFactory->createReport(
|
||||
ReportFactory::CONNECTIONS,
|
||||
$sbasId,
|
||||
[
|
||||
'dmin' => $request->get('dmin'),
|
||||
'dmax' => $request->get('dmax'),
|
||||
'group' => $request->get('group'),
|
||||
'anonymize' => $this->anonymousReport,
|
||||
]
|
||||
);
|
||||
|
||||
$result = Result::create($request, $report->getContent());
|
||||
|
||||
return $result->createResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* route api/report/downloads
|
||||
*
|
||||
* @param Request $request
|
||||
* @param $sbasId
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function downloadsAction(Request $request, $sbasId)
|
||||
{
|
||||
/** @var ReportDownloads $report */
|
||||
$report = $this->reportFactory->createReport(
|
||||
ReportFactory::DOWNLOADS,
|
||||
$sbasId,
|
||||
[
|
||||
'dmin' => $request->get('dmin'),
|
||||
'dmax' => $request->get('dmax'),
|
||||
'group' => $request->get('group'),
|
||||
'bases' => $request->get('base'),
|
||||
'anonymize' => $this->anonymousReport,
|
||||
]
|
||||
);
|
||||
|
||||
$result = Result::create($request, $report->getContent());
|
||||
|
||||
return $result->createResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* route api/report/records
|
||||
*
|
||||
* @param Request $request
|
||||
* @param $sbasId
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function recordsAction(Request $request, $sbasId)
|
||||
{
|
||||
/** @var ReportRecords $report */
|
||||
$report = $this->reportFactory->createReport(
|
||||
ReportFactory::RECORDS,
|
||||
$sbasId,
|
||||
[
|
||||
'dmin' => $request->get('dmin'),
|
||||
'dmax' => $request->get('dmax'),
|
||||
'group' => $request->get('group'),
|
||||
'base' => $request->get('base'),
|
||||
'meta' => $request->get('meta'),
|
||||
]
|
||||
);
|
||||
|
||||
$result = Result::create($request, $report->getContent());
|
||||
|
||||
return $result->createResponse();
|
||||
}
|
||||
|
||||
}
|
195
lib/Alchemy/Phrasea/Report/Controller/ProdReportController.php
Normal file
195
lib/Alchemy/Phrasea/Report/Controller/ProdReportController.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace Alchemy\Phrasea\Report\Controller;
|
||||
|
||||
use Alchemy\Phrasea\Report\Report;
|
||||
use Alchemy\Phrasea\Report\ReportConnections;
|
||||
use Alchemy\Phrasea\Report\ReportDownloads;
|
||||
use Alchemy\Phrasea\Report\ReportFactory;
|
||||
use Alchemy\Phrasea\Report\ReportRecords;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
|
||||
class ProdReportController
|
||||
{
|
||||
private static $mapFromExtension = [
|
||||
'csv' => [
|
||||
'contentType' => 'text/csv',
|
||||
'format' => Report::FORMAT_CSV,
|
||||
],
|
||||
'ods' => [
|
||||
'contentType' => 'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'format' => Report::FORMAT_ODS,
|
||||
],
|
||||
'xlsx' => [
|
||||
'contentType' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'format' => Report::FORMAT_XLSX,
|
||||
],
|
||||
];
|
||||
|
||||
private $reportFactory;
|
||||
private $anonymousReport;
|
||||
private $acl;
|
||||
|
||||
private $extension = null;
|
||||
|
||||
|
||||
/**
|
||||
* @param ReportFactory $reportFactory
|
||||
* @param Bool $anonymousReport
|
||||
* @param \ACL $acl
|
||||
*/
|
||||
public function __construct(ReportFactory $reportFactory, $anonymousReport, \ACL $acl)
|
||||
{
|
||||
$this->reportFactory = $reportFactory;
|
||||
$this->anonymousReport = $anonymousReport;
|
||||
$this->acl = $acl;
|
||||
}
|
||||
|
||||
/**
|
||||
* route prod/report/connections
|
||||
*
|
||||
* @param Request $request
|
||||
* @param $sbasId
|
||||
* @return StreamedResponse
|
||||
*/
|
||||
public function connectionsAction(Request $request, $sbasId)
|
||||
{
|
||||
if(!($extension = $request->get('format'))) {
|
||||
$extension = 'csv';
|
||||
}
|
||||
if(!array_key_exists($extension, self::$mapFromExtension)) {
|
||||
throw new \InvalidArgumentException(sprintf("bad format \"%s\" for report", $extension));
|
||||
}
|
||||
$this->extension = $extension;
|
||||
|
||||
/** @var ReportConnections $report */
|
||||
$report = $this->reportFactory->createReport(
|
||||
ReportFactory::CONNECTIONS,
|
||||
$sbasId,
|
||||
[
|
||||
'dmin' => $request->get('dmin'),
|
||||
'dmax' => $request->get('dmax'),
|
||||
'group' => $request->get('group'),
|
||||
'anonymize' => $this->anonymousReport,
|
||||
]
|
||||
);
|
||||
|
||||
$report->setFormat(self::$mapFromExtension[$this->extension]['format']);
|
||||
|
||||
$response = new StreamedResponse();
|
||||
|
||||
$this->setHeadersFromFormat($response, $report);
|
||||
|
||||
$response->setCallback(function() use($report) {
|
||||
$report->render();
|
||||
});
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* route prod/report/downloads
|
||||
*
|
||||
* @param Request $request
|
||||
* @param $sbasId
|
||||
* @return StreamedResponse
|
||||
*/
|
||||
public function downloadsAction(Request $request, $sbasId)
|
||||
{
|
||||
if(!($extension = $request->get('format'))) {
|
||||
$extension = 'csv';
|
||||
}
|
||||
if(!array_key_exists($extension, self::$mapFromExtension)) {
|
||||
throw new \InvalidArgumentException(sprintf("bad format \"%s\" for report", $extension));
|
||||
}
|
||||
$this->extension = $extension;
|
||||
|
||||
/** @var ReportDownloads $report */
|
||||
$report = $this->reportFactory->createReport(
|
||||
ReportFactory::DOWNLOADS,
|
||||
$sbasId,
|
||||
[
|
||||
'dmin' => $request->get('dmin'),
|
||||
'dmax' => $request->get('dmax'),
|
||||
'group' => $request->get('group'),
|
||||
'bases' => $request->get('base'),
|
||||
'anonymize' => $this->anonymousReport,
|
||||
]
|
||||
);
|
||||
|
||||
$report->setFormat(self::$mapFromExtension[$this->extension]['format']);
|
||||
|
||||
$response = new StreamedResponse();
|
||||
|
||||
$this->setHeadersFromFormat($response, $report);
|
||||
|
||||
$response->setCallback(function() use($report) {
|
||||
$report->render();
|
||||
});
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* route prod/report/records
|
||||
*
|
||||
* @param Request $request
|
||||
* @param $sbasId
|
||||
* @return StreamedResponse
|
||||
*/
|
||||
public function recordsAction(Request $request, $sbasId)
|
||||
{
|
||||
if(!($extension = $request->get('format'))) {
|
||||
$extension = 'csv';
|
||||
}
|
||||
if(!array_key_exists($extension, self::$mapFromExtension)) {
|
||||
throw new \InvalidArgumentException(sprintf("bad format \"%s\" for report", $extension));
|
||||
}
|
||||
$this->extension = $extension;
|
||||
|
||||
/** @var ReportRecords $report */
|
||||
$report = $this->reportFactory->createReport(
|
||||
ReportFactory::RECORDS,
|
||||
$sbasId,
|
||||
[
|
||||
'dmin' => $request->get('dmin'),
|
||||
'dmax' => $request->get('dmax'),
|
||||
'group' => $request->get('group'),
|
||||
'base' => $request->get('base'),
|
||||
'meta' => $request->get('meta'),
|
||||
]
|
||||
);
|
||||
|
||||
$report->setFormat(self::$mapFromExtension[$this->extension]['format']);
|
||||
|
||||
set_time_limit(600);
|
||||
$response = new StreamedResponse();
|
||||
|
||||
$this->setHeadersFromFormat($response, $report);
|
||||
|
||||
$response->setCallback(function() use($report) {
|
||||
$report->render();
|
||||
});
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
private function setHeadersFromFormat($response, Report $report)
|
||||
{
|
||||
$response->headers->set('Content-Type', self::$mapFromExtension[$this->extension]['contentType']);
|
||||
$response->headers->set('Content-Disposition', 'attachment;filename="' . $report->getName() . '"');
|
||||
$response->headers->set('Cache-Control', 'max-age=0');
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace Alchemy\Phrasea\Report\ControllerProvider;
|
||||
|
||||
use Alchemy\Phrasea\Application as PhraseaApplication;
|
||||
use Alchemy\Phrasea\ControllerProvider\Api\Api;
|
||||
use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait;
|
||||
use Alchemy\Phrasea\Core\Event\Listener\OAuthListener;
|
||||
use Alchemy\Phrasea\Report\Controller\ApiReportController;
|
||||
use Alchemy\Phrasea\Report\ReportFactory;
|
||||
use Alchemy\Phrasea\Report\ReportService;
|
||||
use Silex\Application;
|
||||
use Silex\Controller;
|
||||
use Silex\ControllerProviderInterface;
|
||||
use Silex\ServiceProviderInterface;
|
||||
|
||||
|
||||
class ApiReportControllerProvider extends Api implements ControllerProviderInterface, ServiceProviderInterface
|
||||
{
|
||||
use ControllerProviderTrait;
|
||||
|
||||
|
||||
const VERSION = '2.0.0';
|
||||
|
||||
public function register(Application $app)
|
||||
{
|
||||
$app['controller.api.v2.report'] = $app->share(
|
||||
function (PhraseaApplication $app) {
|
||||
return (new ApiReportController(
|
||||
$app['report.factory'],
|
||||
$app['report.service'],
|
||||
$app['conf']->get(['registry', 'modules', 'anonymous-report']),
|
||||
$app->getAclForUser($app->getAuthenticatedUser())
|
||||
));
|
||||
}
|
||||
);
|
||||
|
||||
$app['report.factory'] = $app->share(
|
||||
function (PhraseaApplication $app) {
|
||||
return (new ReportFactory(
|
||||
$app['conf']->get(['main', 'key']),
|
||||
$app['phraseanet.appbox'],
|
||||
$app->getAclForUser($app->getAuthenticatedUser())
|
||||
));
|
||||
}
|
||||
);
|
||||
|
||||
$app['report.service'] = $app->share(
|
||||
function (PhraseaApplication $app) {
|
||||
return (new ReportService(
|
||||
$app['conf']->get(['main', 'key']),
|
||||
$app['phraseanet.appbox'],
|
||||
$app->getAclForUser($app->getAuthenticatedUser())
|
||||
));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
// Intentionally left empty
|
||||
}
|
||||
|
||||
public function connect(Application $app)
|
||||
{
|
||||
if (! $this->isApiEnabled($app)) {
|
||||
return $app['controllers_factory'];
|
||||
}
|
||||
|
||||
$controllers = $this->createCollection($app);
|
||||
/*
|
||||
$firewall = $this->getFirewall($app);
|
||||
|
||||
$controllers->before(function () use ($firewall) {
|
||||
$firewall->requireAccessToModule('report');
|
||||
});
|
||||
*/
|
||||
|
||||
$controllers->before(new OAuthListener());
|
||||
$controllers
|
||||
->get('/', 'controller.api.v2.report:rootAction')
|
||||
;
|
||||
|
||||
$controllers
|
||||
->get('/connections/{sbasId}/', 'controller.api.v2.report:connectionsAction')
|
||||
->assert('sbasId', '\d+')
|
||||
;
|
||||
|
||||
$controllers
|
||||
->get('/downloads/{sbasId}/', 'controller.api.v2.report:downloadsAction')
|
||||
->assert('sbasId', '\d+')
|
||||
;
|
||||
|
||||
$controllers
|
||||
->get('/records/{sbasId}/', 'controller.api.v2.report:recordsAction')
|
||||
->assert('sbasId', '\d+')
|
||||
;
|
||||
|
||||
return $controllers;
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Report\ControllerProvider;
|
||||
|
||||
use Alchemy\Phrasea\Application as PhraseaApplication;
|
||||
use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait;
|
||||
use Alchemy\Phrasea\Report\Controller\ProdReportController;
|
||||
use Alchemy\Phrasea\Report\ReportFactory;
|
||||
use Silex\Application;
|
||||
use Silex\ControllerProviderInterface;
|
||||
use Silex\ServiceProviderInterface;
|
||||
|
||||
|
||||
class ProdReportControllerProvider implements ControllerProviderInterface, ServiceProviderInterface
|
||||
{
|
||||
use ControllerProviderTrait;
|
||||
|
||||
public function register(Application $app)
|
||||
{
|
||||
$app['controller.prod.report'] = $app->share(
|
||||
function (PhraseaApplication $app) {
|
||||
return (new ProdReportController(
|
||||
$app['report.factory'],
|
||||
$app['conf']->get(['registry', 'modules', 'anonymous-report']),
|
||||
$app->getAclForUser($app->getAuthenticatedUser())
|
||||
));
|
||||
}
|
||||
);
|
||||
|
||||
$app['report.factory'] = $app->share(
|
||||
function (PhraseaApplication $app) {
|
||||
return (new ReportFactory(
|
||||
$app['conf']->get(['main', 'key']),
|
||||
$app['phraseanet.appbox'],
|
||||
$app->getAclForUser($app->getAuthenticatedUser())
|
||||
));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function connect(Application $app)
|
||||
{
|
||||
$controllers = $this->createAuthenticatedCollection($app);
|
||||
|
||||
$controllers
|
||||
->get('/connections/{sbasId}/', 'controller.prod.report:connectionsAction')
|
||||
->assert('sbasId', '\d+')
|
||||
;
|
||||
|
||||
$controllers
|
||||
->get('/downloads/{sbasId}/', 'controller.prod.report:downloadsAction')
|
||||
->assert('sbasId', '\d+')
|
||||
;
|
||||
|
||||
$controllers
|
||||
->get('/records/{sbasId}/', 'controller.prod.report:recordsAction')
|
||||
->assert('sbasId', '\d+')
|
||||
;
|
||||
|
||||
return $controllers;
|
||||
}
|
||||
}
|
157
lib/Alchemy/Phrasea/Report/Report.php
Normal file
157
lib/Alchemy/Phrasea/Report/Report.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Report;
|
||||
|
||||
use Alchemy\Phrasea\Application;
|
||||
use Alchemy\Phrasea\Out\Module\Excel;
|
||||
|
||||
|
||||
abstract class Report
|
||||
{
|
||||
const FORMAT_CSV = 'format_csv';
|
||||
const FORMAT_ODS = 'format_ods';
|
||||
// const FORMAT_XLS = 'format_xls';
|
||||
const FORMAT_XLSX = 'format_xlsx';
|
||||
|
||||
private $format = self::FORMAT_CSV;
|
||||
|
||||
/** @var \databox */
|
||||
protected $databox;
|
||||
protected $parms;
|
||||
|
||||
public function __construct(\databox $databox, $parms)
|
||||
{
|
||||
$this->databox = $databox;
|
||||
$this->parms = $parms;
|
||||
|
||||
$this->databox->get_connection()->getWrappedConnection()->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, FALSE);
|
||||
}
|
||||
|
||||
abstract function getName();
|
||||
|
||||
abstract function getColumnTitles();
|
||||
|
||||
abstract function getKeyName();
|
||||
|
||||
abstract function getAllRows($callback);
|
||||
|
||||
protected function getDatabox()
|
||||
{
|
||||
return $this->databox;
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
$ret = [];
|
||||
$this->getAllRows(
|
||||
function($row) use($ret) {
|
||||
$ret[] = $row;
|
||||
}
|
||||
);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* get quoted coll id's granted for report, possibly filtered by
|
||||
* baseIds : only from this list of bases
|
||||
*
|
||||
* @param \ACL $acl
|
||||
* @param int[]|null $baseIds
|
||||
* @return array
|
||||
*/
|
||||
protected function getCollIds(\ACL $acl, $baseIds)
|
||||
{
|
||||
$ret = [];
|
||||
/** @var \collection $collection */
|
||||
foreach($acl->get_granted_base([\ACL::CANREPORT]) as $collection) {
|
||||
if($collection->get_sbas_id() != $this->databox->get_sbas_id()) {
|
||||
continue;
|
||||
}
|
||||
if(!is_null($baseIds) && !in_array($collection->get_base_id(), $baseIds)) {
|
||||
continue;
|
||||
}
|
||||
$ret[] = $this->databox->get_connection()->quote($collection->get_coll_id());
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function setFormat($format)
|
||||
{
|
||||
if(!in_array($format, [
|
||||
//self::FORMAT_XLS,
|
||||
self::FORMAT_CSV,
|
||||
self::FORMAT_ODS,
|
||||
self::FORMAT_XLSX,
|
||||
])) {
|
||||
throw new \InvalidArgumentException(sprintf("bad format \"%s\" for report", $format));
|
||||
}
|
||||
$this->format = $format;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFormat()
|
||||
{
|
||||
return $this->format;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
switch($this->format) {
|
||||
//case self::FORMAT_XLS:
|
||||
case self::FORMAT_CSV:
|
||||
case self::FORMAT_ODS:
|
||||
case self::FORMAT_XLSX:
|
||||
$this->renderAsExcel();
|
||||
break;
|
||||
default:
|
||||
// should not happen since format is checked before
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function renderAsExcel()
|
||||
{
|
||||
switch($this->format) {
|
||||
//case self::FORMAT_XLS:
|
||||
// $excel = new Excel(Excel::FORMAT_XLS);
|
||||
// header('Content-Type: application/vnd.ms-excel');
|
||||
// break;
|
||||
case self::FORMAT_XLSX:
|
||||
$excel = new Excel(Excel::FORMAT_XLSX, $this->getName() . ".xlsx");
|
||||
break;
|
||||
case self::FORMAT_ODS:
|
||||
$excel = new Excel(Excel::FORMAT_ODS, $this->getName() . ".ods");
|
||||
break;
|
||||
case self::FORMAT_CSV:
|
||||
default:
|
||||
$excel = new Excel(Excel::FORMAT_CSV, $this->getName() . ".csv");
|
||||
break;
|
||||
}
|
||||
|
||||
$excel->addRow($this->getColumnTitles());
|
||||
|
||||
$n = 0;
|
||||
$this->getAllRows(
|
||||
function($row) use($excel, $n) {
|
||||
$excel->addRow($row);
|
||||
if($n++ % 10000 === 0) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$excel->render();
|
||||
}
|
||||
|
||||
}
|
149
lib/Alchemy/Phrasea/Report/ReportConnections.php
Normal file
149
lib/Alchemy/Phrasea/Report/ReportConnections.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Report;
|
||||
|
||||
use Alchemy\Phrasea\Application;
|
||||
use Alchemy\Phrasea\Exception\InvalidArgumentException;
|
||||
|
||||
|
||||
class ReportConnections extends Report
|
||||
{
|
||||
private $appKey;
|
||||
|
||||
/* those vars will be set once by computeVars() */
|
||||
private $name = null;
|
||||
private $sql = null;
|
||||
private $columnTitles = [];
|
||||
private $keyName = null;
|
||||
|
||||
|
||||
public function getColumnTitles()
|
||||
{
|
||||
$this->computeVars();
|
||||
return $this->columnTitles;
|
||||
}
|
||||
|
||||
public function getKeyName()
|
||||
{
|
||||
$this->computeVars();
|
||||
return $this->keyName;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$this->computeVars();
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setAppKey($appKey)
|
||||
{
|
||||
$this->appKey = $appKey;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAllRows($callback)
|
||||
{
|
||||
$this->computeVars();
|
||||
$stmt = $this->databox->get_connection()->executeQuery($this->sql, []);
|
||||
while (($row = $stmt->fetch())) {
|
||||
$callback($row);
|
||||
}
|
||||
$stmt->closeCursor();
|
||||
}
|
||||
|
||||
private function computeVars()
|
||||
{
|
||||
if(!is_null($this->name)) {
|
||||
// vars already computed
|
||||
return;
|
||||
}
|
||||
|
||||
switch($this->parms['group']) {
|
||||
case null:
|
||||
$this->name = "Connections";
|
||||
$this->columnTitles = ['id', 'date', 'usrid', 'user', 'fonction', 'societe', 'activite', 'pays', 'nav', 'version', 'os', 'res', 'ip', 'user_agent'];
|
||||
if($this->parms['anonymize']) {
|
||||
$sql = "SELECT `id`, `date`,\n"
|
||||
. " `usrid`, '-' AS `user`, '-' AS `fonction`, '-' AS `societe`, '-' AS `activite`, '-' AS `pays`,\n"
|
||||
. " `nav`, `version`, `os`, `res`, `ip`, `user_agent` FROM `log`\n"
|
||||
. " WHERE {{GlobalFilter}}";
|
||||
}
|
||||
else {
|
||||
$sql = "SELECT `id`, `date`,\n"
|
||||
. " `usrid`, `user`, `fonction`, `societe`, `activite`, `pays`,\n"
|
||||
. " `nav`, `version`, `os`, `res`, `ip`, `user_agent` FROM `log`\n"
|
||||
. " WHERE {{GlobalFilter}}";
|
||||
}
|
||||
$this->keyName = null;
|
||||
break;
|
||||
case 'user':
|
||||
$this->name = "Connections per user";
|
||||
$this->columnTitles = ['user_id', 'user', 'fonction', 'societe', 'activite', 'pays', 'min_date', 'max_date', 'nb'];
|
||||
if($this->parms['anonymize']) {
|
||||
$sql = "SELECT `usrid`, '-' AS `user`, '-' AS `fonction`, '-' AS `societe`, '-' AS `activite`, '-' AS `pays`,\n"
|
||||
. " MIN(`date`) AS `dmin`, MAX(`date`) AS `dmax`, SUM(1) AS `nb` FROM `log`\n"
|
||||
. " WHERE {{GlobalFilter}}\n"
|
||||
. " GROUP BY `usrid`\n"
|
||||
. " ORDER BY `nb` DESC";
|
||||
}
|
||||
else {
|
||||
$sql = "SELECT `usrid`, `user`, `fonction`, `societe`, `activite`, `pays`,\n"
|
||||
. " MIN(`date`) AS `dmin`, MAX(`date`) AS `dmax`, SUM(1) AS `nb` FROM `log`\n"
|
||||
. " WHERE {{GlobalFilter}}\n"
|
||||
. " GROUP BY `usrid`\n"
|
||||
. " ORDER BY `nb` DESC";
|
||||
}
|
||||
$this->keyName = 'usrid';
|
||||
break;
|
||||
case 'nav':
|
||||
case 'nav,version':
|
||||
case 'os':
|
||||
case 'os,nav':
|
||||
case 'os,nav,version':
|
||||
case 'res':
|
||||
$this->name = "Connections per " . $this->parms['group'];
|
||||
$groups = explode(',', $this->parms['group']);
|
||||
$qgroups = implode(
|
||||
',',
|
||||
array_map(function($g) {return '`'.$g.'`';}, $groups)
|
||||
);
|
||||
$this->columnTitles = $groups;
|
||||
$this->columnTitles[] = 'nb';
|
||||
$sql = "SELECT " . $qgroups . ", SUM(1) AS `nb` FROM `log`\n"
|
||||
. " WHERE {{GlobalFilter}}\n"
|
||||
. " GROUP BY " . $qgroups . "\n"
|
||||
. " ORDER BY `nb` DESC"
|
||||
;
|
||||
$this->keyName = null;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('invalid "group" argument');
|
||||
break;
|
||||
}
|
||||
|
||||
$filter = "`usrid`>0";
|
||||
// next line : comment to disable "site", to test on an imported dataset from another instance
|
||||
$filter .= " AND `site` = " . $this->databox->get_connection()->quote($this->appKey);
|
||||
|
||||
if($this->parms['dmin']) {
|
||||
$filter .= "\n AND `log`.`date` >= " . $this->databox->get_connection()->quote($this->parms['dmin']);
|
||||
}
|
||||
if($this->parms['dmax']) {
|
||||
$filter .= "\n AND `log`.`date` <= " . $this->databox->get_connection()->quote($this->parms['dmax']);
|
||||
}
|
||||
|
||||
$this->sql = str_replace('{{GlobalFilter}}', $filter, $sql);
|
||||
|
||||
// file_put_contents("/tmp/phraseanet-log.txt", sprintf("%s (%d) %s\n", __FILE__, __LINE__, var_export($this->sql, true)), FILE_APPEND);
|
||||
}
|
||||
|
||||
}
|
179
lib/Alchemy/Phrasea/Report/ReportDownloads.php
Normal file
179
lib/Alchemy/Phrasea/Report/ReportDownloads.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Report;
|
||||
|
||||
use Alchemy\Phrasea\Application;
|
||||
use Alchemy\Phrasea\Exception\InvalidArgumentException;
|
||||
|
||||
|
||||
class ReportDownloads extends Report
|
||||
{
|
||||
private $appKey;
|
||||
|
||||
/** @var \ACL */
|
||||
private $acl;
|
||||
|
||||
/* those vars will be set once by computeVars() */
|
||||
private $name = null;
|
||||
private $sql = null;
|
||||
private $columnTitles = [];
|
||||
private $keyName = null;
|
||||
|
||||
|
||||
public function getColumnTitles()
|
||||
{
|
||||
$this->computeVars();
|
||||
return $this->columnTitles;
|
||||
}
|
||||
|
||||
public function getKeyName()
|
||||
{
|
||||
$this->computeVars();
|
||||
return $this->keyName;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$this->computeVars();
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setAppKey($appKey)
|
||||
{
|
||||
$this->appKey = $appKey;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setACL($acl)
|
||||
{
|
||||
$this->acl = $acl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAllRows($callback)
|
||||
{
|
||||
$this->computeVars();
|
||||
$stmt = $this->databox->get_connection()->executeQuery($this->sql, []);
|
||||
while (($row = $stmt->fetch())) {
|
||||
$callback($row);
|
||||
}
|
||||
$stmt->closeCursor();
|
||||
}
|
||||
|
||||
private function computeVars()
|
||||
{
|
||||
if(!is_null($this->name)) {
|
||||
// vars already computed
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($this->parms['group']) {
|
||||
case null:
|
||||
$this->name = "Downloads";
|
||||
$this->columnTitles = ['id', 'usrid', 'user', 'fonction', 'societe', 'activite', 'pays', 'date', 'record_id', 'coll_id', 'subdef'];
|
||||
if($this->parms['anonymize']) {
|
||||
$sql = "SELECT `ld`.`id`, `l`.`usrid`, '-' AS `user`, '-' AS `fonction`, '-' AS `societe`, '-' AS `activite`, '-' AS `pays`,\n"
|
||||
. " `ld`.`date`, `ld`.`record_id`, `ld`.`coll_id`, `ld`.`final`"
|
||||
. " FROM `log_docs` AS `ld` INNER JOIN `log` AS `l` ON `l`.`id`=`ld`.`log_id`\n"
|
||||
. " WHERE {{GlobalFilter}}";
|
||||
}
|
||||
else {
|
||||
$sql = "SELECT `ld`.`id`, `l`.`usrid`, `l`.`user`, `l`.`fonction`, `l`.`societe`, `l`.`activite`, `l`.`pays`,\n"
|
||||
. " `ld`.`date`, `ld`.`record_id`, `ld`.`coll_id`, `ld`.`final`"
|
||||
. " FROM `log_docs` AS `ld` INNER JOIN `log` AS `l` ON `l`.`id`=`ld`.`log_id`\n"
|
||||
. " WHERE {{GlobalFilter}}";
|
||||
}
|
||||
$this->keyName = 'id';
|
||||
break;
|
||||
case 'user':
|
||||
$this->name = "Downloads by user";
|
||||
$this->columnTitles = ['usrid', 'user', 'fonction', 'societe', 'activite', 'pays', 'min_date', 'max_date', 'nb'];
|
||||
if($this->parms['anonymize']) {
|
||||
$sql = "SELECT `l`.`usrid`, '-' AS `user`, '-' AS `fonction`, '-' AS `societe`, '-' AS `activite`, '-' AS `pays`,\n"
|
||||
. " MIN(`ld`.`date`) AS `dmin`, MAX(`ld`.`date`) AS `dmax`, SUM(1) AS `nb`\n"
|
||||
. " FROM `log_docs` AS `ld` INNER JOIN `log` AS `l` ON `l`.`id`=`ld`.`log_id`\n"
|
||||
. " WHERE {{GlobalFilter}}\n"
|
||||
. " GROUP BY `l`.`usrid`\n"
|
||||
. " ORDER BY `nb` DESC";
|
||||
}
|
||||
else {
|
||||
$sql = "SELECT `l`.`usrid`, `l`.`user`, `l`.`fonction`, `l`.`societe`, `l`.`activite`, `l`.`pays`,\n"
|
||||
. " MIN(`ld`.`date`) AS `dmin`, MAX(`ld`.`date`) AS `dmax`, SUM(1) AS `nb`\n"
|
||||
. " FROM `log_docs` AS `ld` INNER JOIN `log` AS `l` ON `l`.`id`=`ld`.`log_id`\n"
|
||||
. " WHERE {{GlobalFilter}}\n"
|
||||
. " GROUP BY `l`.`usrid`\n"
|
||||
. " ORDER BY `nb` DESC";
|
||||
}
|
||||
$this->keyName = 'usrid';
|
||||
break;
|
||||
case 'record':
|
||||
$this->name = "Downloads by record";
|
||||
$this->columnTitles = ['record_id', 'min_date', 'max_date', 'nb'];
|
||||
$sql = "SELECT `ld`.`record_id`,\n"
|
||||
. " MIN(`ld`.`date`) AS `dmin`, MAX(`ld`.`date`) AS `dmax`, SUM(1) AS `nb`\n"
|
||||
. " FROM `log_docs` AS `ld` INNER JOIN `log` AS `l` ON `l`.`id`=`ld`.`log_id`\n"
|
||||
. " WHERE {{GlobalFilter}}\n"
|
||||
. " GROUP BY `l`.`usrid`\n"
|
||||
. " ORDER BY `nb` DESC"
|
||||
;
|
||||
$this->keyName = 'record_id';
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('invalid "group" argument');
|
||||
break;
|
||||
}
|
||||
|
||||
// get acl-filtered coll_id(s) as already sql-quoted
|
||||
$collIds = $this->getCollIds($this->acl, $this->parms['bases']);
|
||||
|
||||
if(!empty($collIds)) {
|
||||
|
||||
// filter subdefs by class
|
||||
$subdefsToReport = ['document' => $this->databox->get_connection()->quote('document')];
|
||||
foreach ($this->getDatabox()->get_subdef_structure() as $subGroup) {
|
||||
foreach ($subGroup->getIterator() as $sub) {
|
||||
if(in_array($sub->get_class(), ['document', 'preview'])) {
|
||||
// keep only unique names
|
||||
$subdefsToReport[$sub->get_name()] = $this->databox->get_connection()->quote($sub->get_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$subdefsToReport = join(',', $subdefsToReport);
|
||||
|
||||
$filter = "`action`='download' AND `ld`.`coll_id` IN(" . join(',', $collIds) . ")\n"
|
||||
. " AND `l`.`usrid`>0\n"
|
||||
. " AND `ld`.`final` IN(" . $subdefsToReport . ")";
|
||||
|
||||
// next line : comment to disable "site", to test on an imported dataset from another instance
|
||||
$filter .= "\n AND `l`.`site` = " . $this->databox->get_connection()->quote($this->appKey);
|
||||
|
||||
if($this->parms['dmin']) {
|
||||
$filter .= "\n AND `ld`.`date` >= " . $this->databox->get_connection()->quote($this->parms['dmin']);
|
||||
}
|
||||
if($this->parms['dmax']) {
|
||||
$filter .= "\n AND `ld`.`date` <= " . $this->databox->get_connection()->quote($this->parms['dmax']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// no collections report ?
|
||||
// keep the sql intact (to match placeholders/parameters), but enforce empty result
|
||||
$filter = "FALSE";
|
||||
}
|
||||
|
||||
$this->sql = str_replace('{{GlobalFilter}}', $filter, $sql);
|
||||
|
||||
// file_put_contents("/tmp/phraseanet-log.txt", sprintf("%s (%d) %s\n", __FILE__, __LINE__, $this->sql), FILE_APPEND);
|
||||
}
|
||||
|
||||
}
|
105
lib/Alchemy/Phrasea/Report/ReportFactory.php
Normal file
105
lib/Alchemy/Phrasea/Report/ReportFactory.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Report;
|
||||
|
||||
use Alchemy\Phrasea\Application;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
|
||||
/**
|
||||
* Class ReportFactory
|
||||
*
|
||||
* published as service $app['report.factory']
|
||||
*
|
||||
* @package Alchemy\Phrasea\Report
|
||||
*/
|
||||
class ReportFactory
|
||||
{
|
||||
const CONNECTIONS = 'connections';
|
||||
const DOWNLOADS = 'downloads';
|
||||
const RECORDS = 'records';
|
||||
|
||||
protected $appKey;
|
||||
protected $appbox;
|
||||
protected $databox;
|
||||
protected $acl;
|
||||
|
||||
/**
|
||||
* @param string $appKey
|
||||
* @param \appbox $appbox
|
||||
* @param \ACL acl
|
||||
*/
|
||||
public function __construct($appKey, \appbox $appbox, \ACL $acl)
|
||||
{
|
||||
$this->appKey = $appKey;
|
||||
$this->appbox = $appbox;
|
||||
$this->acl = $acl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $table
|
||||
* @param null $sbasId
|
||||
* @param null $parms
|
||||
*
|
||||
* @return ReportConnections | ReportDownloads
|
||||
*/
|
||||
public function createReport($table, $sbasId=null, $parms=null)
|
||||
{
|
||||
switch($table) {
|
||||
case self::CONNECTIONS:
|
||||
return (new ReportConnections(
|
||||
$this->findDbOr404($sbasId),
|
||||
$parms
|
||||
))
|
||||
->setAppKey($this->appKey)
|
||||
;
|
||||
break;
|
||||
|
||||
case self::DOWNLOADS:
|
||||
return (new ReportDownloads(
|
||||
$this->findDbOr404($sbasId),
|
||||
$parms
|
||||
))
|
||||
->setAppKey($this->appKey)
|
||||
->setACL($this->acl)
|
||||
;
|
||||
break;
|
||||
|
||||
case self::RECORDS:
|
||||
return (new ReportRecords(
|
||||
$this->findDbOr404($sbasId),
|
||||
$parms
|
||||
))
|
||||
->setACL($this->acl)
|
||||
;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf("unknown table type \"%s\"", $table));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $sbasId
|
||||
* @return \databox
|
||||
*/
|
||||
private function findDbOr404($sbasId)
|
||||
{
|
||||
$db = $this->appbox->get_databox(($sbasId));
|
||||
if(!$db) {
|
||||
throw new NotFoundHttpException(sprintf('Databox %s not found', $sbasId));
|
||||
}
|
||||
|
||||
return $db;
|
||||
}
|
||||
|
||||
}
|
135
lib/Alchemy/Phrasea/Report/ReportRecords.php
Normal file
135
lib/Alchemy/Phrasea/Report/ReportRecords.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Report;
|
||||
|
||||
use Alchemy\Phrasea\Application;
|
||||
|
||||
|
||||
class ReportRecords extends Report
|
||||
{
|
||||
/** @var \ACL */
|
||||
private $acl;
|
||||
|
||||
/* those vars will be set once by computeVars() */
|
||||
private $name = null;
|
||||
private $sqlWhere = null;
|
||||
private $sqlColSelect = null;
|
||||
private $columnTitles = null;
|
||||
private $keyName = null;
|
||||
|
||||
|
||||
public function getColumnTitles()
|
||||
{
|
||||
$this->computeVars();
|
||||
return $this->columnTitles;
|
||||
}
|
||||
|
||||
public function getKeyName()
|
||||
{
|
||||
$this->computeVars();
|
||||
return $this->keyName;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$this->computeVars();
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setACL($acl)
|
||||
{
|
||||
$this->acl = $acl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAllRows($callback)
|
||||
{
|
||||
$this->computeVars();
|
||||
|
||||
$lastRid = 0;
|
||||
while(true) {
|
||||
$sql = "SELECT MIN(record_id) AS `from`, MAX(record_id) AS `to` FROM (\n"
|
||||
. "SELECT record_id FROM record AS `r`\n"
|
||||
. "WHERE " . $this->sqlWhere . " AND record_id>" . $lastRid . " LIMIT 5000) AS _t";
|
||||
$stmt = $this->databox->get_connection()->executeQuery($sql, []);
|
||||
$row = $stmt->fetch();
|
||||
$stmt->closeCursor();
|
||||
|
||||
if($row && !is_null($row['from']) && !is_null($row['to'])) {
|
||||
$sql = "SELECT r.record_id, c.asciiname, r.moddate, r.mime, r.type, r.originalname,\n"
|
||||
. $this->sqlColSelect . "\n"
|
||||
. "FROM (`record` AS `r` LEFT JOIN `coll` AS `c` USING(`coll_id`)) LEFT JOIN `metadatas` AS `m` USING(`record_id`)\n"
|
||||
. "WHERE " . $this->sqlWhere . "\n"
|
||||
. " AND r.record_id >= " . $row['from'] . " AND r.record_id <= " . $row['to'] . "\n"
|
||||
. "GROUP BY `record_id`\n";
|
||||
|
||||
// file_put_contents("/tmp/phraseanet-log.txt", sprintf("%s (%d) %s\n", __FILE__, __LINE__, var_export($sql, true)), FILE_APPEND);
|
||||
|
||||
$stmt = $this->databox->get_connection()->executeQuery($sql, []);
|
||||
$rows = $stmt->fetchAll();
|
||||
$stmt->closeCursor();
|
||||
foreach($rows as $row) {
|
||||
$callback($row);
|
||||
$lastRid = $row['record_id'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function computeVars()
|
||||
{
|
||||
if(!is_null($this->name)) {
|
||||
// vars already computed
|
||||
return;
|
||||
}
|
||||
|
||||
// pivot-like query on metadata fields
|
||||
$this->sqlColSelect = [];
|
||||
$this->columnTitles = ['record_id', 'collection', 'moddate', 'mime', 'type', 'originalname'];
|
||||
foreach($this->getDatabox()->get_meta_structure() as $field) {
|
||||
// skip the fields that can't be reported
|
||||
if(!$field->is_report() || ($field->isBusiness() && !$this->acl->can_see_business_fields($this->getDatabox()))) {
|
||||
continue;
|
||||
}
|
||||
// if a list of meta was provided, just keep those
|
||||
if(is_array($this->parms['meta']) && !in_array($field->get_name(), $this->parms['meta'])) {
|
||||
continue;
|
||||
}
|
||||
// column names is not important in the result, simply match the 'title' position
|
||||
$this->columnTitles[] = $field->get_name();
|
||||
$this->sqlColSelect[] = sprintf("GROUP_CONCAT(IF(`m`.`meta_struct_id`=%s, `m`.`value`, NULL)) AS `f%s`", $field->get_id(), $field->get_id());
|
||||
}
|
||||
|
||||
$this->sqlColSelect = join(",\n", $this->sqlColSelect);
|
||||
|
||||
// get acl-filtered coll_id(s) as already sql-quoted
|
||||
$collIds = $this->getCollIds($this->acl, $this->parms['base']);
|
||||
if(!empty($collIds)) {
|
||||
$this->sqlWhere = "`r`.`parent_record_id`=0 AND `r`.`coll_id` IN(" . join(',', $collIds) . ")";
|
||||
if(!is_null($this->parms['dmin'])) {
|
||||
$this->sqlWhere .= " AND r.moddate >= " . $this->databox->get_connection()->quote($this->parms['dmin']);
|
||||
}
|
||||
if(!is_null($this->parms['dmax'])) {
|
||||
$this->sqlWhere .= " AND r.moddate <= " . $this->databox->get_connection()->quote($this->parms['dmax']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->sqlWhere = "FALSE";
|
||||
}
|
||||
|
||||
$this->name = "Databox";
|
||||
}
|
||||
|
||||
}
|
62
lib/Alchemy/Phrasea/Report/ReportService.php
Normal file
62
lib/Alchemy/Phrasea/Report/ReportService.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Report;
|
||||
|
||||
use Alchemy\Phrasea\Application;
|
||||
|
||||
|
||||
class ReportService
|
||||
{
|
||||
protected $appKey;
|
||||
protected $appbox;
|
||||
protected $acl;
|
||||
|
||||
/**
|
||||
* @param string $appKey
|
||||
* @param \appbox $appbox
|
||||
* @param \ACL acl
|
||||
*/
|
||||
public function __construct($appKey, \appbox $appbox, \ACL $acl)
|
||||
{
|
||||
$this->appKey = $appKey;
|
||||
$this->appbox = $appbox;
|
||||
$this->acl = $acl;
|
||||
}
|
||||
|
||||
/**
|
||||
* return bases allowed for reporting, grouped by databox
|
||||
* @return array
|
||||
*/
|
||||
public function getGranted()
|
||||
{
|
||||
$databoxes = [];
|
||||
|
||||
/** @var \collection $collection */
|
||||
foreach ($this->acl->get_granted_base([\ACL::CANREPORT]) as $collection) {
|
||||
$sbas_id = $collection->get_sbas_id();
|
||||
if (!isset($databoxes[$sbas_id])) {
|
||||
$databoxes[$sbas_id] = [
|
||||
'id' => $sbas_id,
|
||||
'name' => $collection->get_databox()->get_viewname(),
|
||||
'collections' => []
|
||||
];
|
||||
}
|
||||
$databoxes[$sbas_id]['collections'][$collection->get_base_id()] = [
|
||||
'id' => $collection->get_base_id(),
|
||||
'coll_id' => $collection->get_coll_id(),
|
||||
'name' => $collection->get_name()
|
||||
];
|
||||
}
|
||||
|
||||
return ['databoxes' => $databoxes];
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user