Basic export function for #2667

This commit is contained in:
James Cole
2019-12-27 10:59:31 +01:00
parent 77647a8597
commit c06b9d8045
10 changed files with 484 additions and 76 deletions

View File

@@ -0,0 +1,63 @@
<?php
/**
* ExportData.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Console\Commands;
use Illuminate\Console\Command;
/**
* Class ExportData
*/
class ExportData extends Command
{
/**
* The console command description.
*
* @var string
*/
protected $description = 'I export data.';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:export-data';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
//
}
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* IndexController.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Http\Controllers\Export;
use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\Export\ExportFileGenerator;
use Illuminate\Http\Response as LaravelResponse;
/**
* Class IndexController
*/
class IndexController extends Controller
{
/** @var JournalRepositoryInterface */
private $journalRepository;
/**
* IndexController constructor.
*
* @codeCoverageIgnore
*/
public function __construct()
{
parent::__construct();
// translations:
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-life-bouy');
app('view')->share('title', (string)trans('firefly.export_data_title'));
$this->journalRepository = app(JournalRepositoryInterface::class);
return $next($request);
}
);
}
/**
*
*/
public function export()
{
/** @var ExportFileGenerator $generator */
$generator = app(ExportFileGenerator::class);
// get first transaction in DB:
$firstDate = new Carbon;
$firstDate->subYear();
$journal = $this->journalRepository->firstNull();
if (null !== $journal) {
$firstDate = clone $journal->date;
}
$generator->setStart($firstDate);
$result = $generator->export();
$name = sprintf('%s_transaction_export.csv', date('Y_m_d'));
$quoted = sprintf('"%s"', addcslashes($name, '"\\'));
// headers for CSV file.
/** @var LaravelResponse $response */
$response = response($result['transactions'], 200);
$response
->header('Content-Description', 'File Transfer')
->header('Content-Type', 'text/x-csv')
->header('Content-Disposition', 'attachment; filename=' . $quoted)
//->header('Content-Transfer-Encoding', 'binary')
->header('Connection', 'Keep-Alive')
->header('Expires', '0')
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
->header('Pragma', 'public')
->header('Content-Length', strlen($result['transactions']));
return $response;
// return CSV file made from 'transactions' array.
return $result['transactions'];
}
/**
*
*/
public function index()
{
return view('export.index');
}
}

View File

@@ -0,0 +1,166 @@
<?php
/**
* ExportFileGenerator.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Support\Export;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use League\Csv\Writer;
/**
* Class ExportFileGenerator
*/
class ExportFileGenerator
{
/** @var Carbon */
private $end;
/** @var bool */
private $exportTransactions;
/** @var Carbon */
private $start;
public function __construct()
{
$this->start = new Carbon;
$this->start->subYear();
$this->end = new Carbon;
$this->exportTransactions = true;
}
/**
* @return array
*/
public function export(): array
{
$return = [];
if ($this->exportTransactions) {
$return['transactions'] = $this->exportTransactions();
}
return $return;
}
/**
* @param Carbon $end
*/
public function setEnd(Carbon $end): void
{
$this->end = $end;
}
/**
* @param bool $exportTransactions
*/
public function setExportTransactions(bool $exportTransactions): void
{
$this->exportTransactions = $exportTransactions;
}
/**
* @param Carbon $start
*/
public function setStart(Carbon $start): void
{
$this->start = $start;
}
/**
* @return string
*/
private function exportTransactions(): string
{
// TODO better place for keys?
$header = [
'user_id',
'group_id',
'journal_id',
'created_at',
'updated_at',
'group_title',
'type',
'amount',
'foreign_amount',
'currency_code',
'foreign_currency_code',
'description',
'date',
'source_name',
'source_iban',
'source_type',
'destination_name',
'destination_iban',
'destination_type',
'reconciled',
'category',
'budget',
'bill',
'tags',
];
$collector = app(GroupCollectorInterface::class);
$collector->setRange($this->start, $this->end)->withAccountInformation()->withCategoryInformation()->withBillInformation()
->withBudgetInformation();
$journals = $collector->getExtractedJournals();
$records = [];
/** @var array $journal */
foreach ($journals as $journal) {
$records[] = [
$journal['user_id'],
$journal['transaction_group_id'],
$journal['transaction_journal_id'],
$journal['created_at']->toAtomString(),
$journal['updated_at']->toAtomString(),
$journal['transaction_group_title'],
$journal['transaction_type_type'],
$journal['amount'],
$journal['foreign_amount'],
$journal['currency_code'],
$journal['foreign_currency_code'],
$journal['description'],
$journal['date']->toAtomString(),
$journal['source_account_name'],
$journal['source_account_iban'],
$journal['source_account_type'],
$journal['destination_account_name'],
$journal['destination_account_iban'],
$journal['destination_account_type'],
$journal['reconciled'],
$journal['category_name'],
$journal['budget_name'],
$journal['bill_name'],
implode(',', $journal['tags']),
];
}
//load the CSV document from a string
$csv = Writer::createFromString('');
//insert the header
$csv->insertOne($header);
//insert all the records
$csv->insertAll($records);
return $csv->getContent(); //returns the CSV document as a string
}
}

View File

@@ -132,6 +132,9 @@ return [
'single_user_mode' => true,
'is_demo_site' => false,
],
'feature_flags' => [
'export' => false,
],
'encryption' => null === env('USE_ENCRYPTION') || env('USE_ENCRYPTION') === true,
'version' => '5.0.0-alpha.1',
'api_version' => '1.0.0',

View File

@@ -613,6 +613,16 @@ return [
'login_provider_local_only' => 'This action is not available when authenticating through ":login_provider".',
'delete_local_info_only' => 'Because you authenticate through ":login_provider", this will only delete local Firefly III information.',
// export data:
'import_and_export_menu' => 'Import and export',
'export_data_title' => 'Export data from Firefly III',
'export_data_menu' => 'Export data',
'export_data_bc' => 'Export data from Firefly III',
'export_data_main_title' => 'Export data from Firefly III',
'export_data_expl' => 'This link allows you to export all transactions from Firefly III, with all the relevant meta-data. Please refer to the help (top right (?)-icon) for more information about the process.',
'export_data_all_transactions' => 'Export all transactions',
'export_data_advanced_expl' => 'If you want to have more advanced options, consult the command line options. More information is available in the help (top right (?)-icon) or consult the console command: <code>php artisan help firefly-iii:export-data</code>',
// attachments
'nr_of_attachments' => 'One attachment|:count attachments',
'attachments' => 'Attachments',

View File

@@ -0,0 +1,35 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.render(Route.getCurrentRoute.getName) }}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">{{ 'export_data_main_title'|_ }}</h3>
</div>
<div class="box-body">
<p>
{{ 'export_data_expl'|_ }}
</p>
<ul>
<li><i class="fa fa-fw fa-download"></i> <a href="{{ route('export.export') }}" title="{{ 'export_data_all_transactions'|_ }}">{{ 'export_data_all_transactions'|_ }}</a></li>
</ul>
<p>
{{ 'export_data_advanced_expl'|_ }}
</p>
</div>
</div>
</div>
</div>
{% endblock %}
{% block styles %}
{% endblock %}
{% block scripts %}
{% endblock %}

View File

@@ -58,13 +58,36 @@
<span>{{ 'reports'|_ }}</span>
</a>
</li>
<li class="{{ activeRoutePartial('import') }} {{ activeRoutePartial('export') }} treeview" id="transaction-menu">
<a href="#">
<i class="fa fa-hdd-o fa-fw"></i>
{% if config('firefly.feature_flags.export') %}
<span>{{ 'import_and_export_menu'|_ }}</span>
{% else %}
<span>{{ 'import_transactions'|_ }}</span>
{% endif %}
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li class="{{ activeRoutePartial('import') }}">
<a href="{{ route('import.index') }}">
<i class="fa fa-archive fa-fw"></i>
<span>{{ 'import_transactions'|_ }}</span>
</a>
</li>
{% if config('firefly.feature_flags.export') %}
<li class="{{ activeRoutePartial('export') }}">
<a href="{{ route('export.index') }}">
<i class="fa fa-life-bouy fa-fw"></i>
<span>{{ 'export_data_menu'|_ }}</span>
</a>
</li>
{% endif %}
</ul>
</li>
<li class="{{ activeRoutePartial('transactions') }} treeview" id="transaction-menu">
<a href="#">

View File

@@ -563,7 +563,7 @@ try {
'export.index',
function (BreadcrumbsGenerator $breadcrumbs) {
$breadcrumbs->parent('home');
$breadcrumbs->push(trans('firefly.export_data'), route('export.index'));
$breadcrumbs->push(trans('firefly.export_data_bc'), route('export.index'));
}
);

View File

@@ -22,14 +22,3 @@
declare(strict_types=1);
/*
|--------------------------------------------------------------------------
| Console Routes
|--------------------------------------------------------------------------
|
| This file is where you may define all of your Closure based console
| commands. Each Closure is bound to a command instance allowing a
| simple approach to interacting with each command's IO methods.
|
*/

View File

@@ -488,7 +488,17 @@ Route::group(
}
);
/**
* Export controller
*/
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'export', 'as' => 'export.'], static function () {
// index
Route::get('', ['uses' => 'Export\IndexController@index', 'as' => 'index']);
Route::get('export', ['uses' => 'Export\IndexController@export', 'as' => 'export']);
});
/**
* Import Controller
*/