New export functionality.

This commit is contained in:
James Cole
2016-02-04 17:16:16 +01:00
parent d7a66f6782
commit 86c22c9fdd
24 changed files with 1311 additions and 13 deletions

View File

@@ -0,0 +1,54 @@
<?php
/**
* AttachmentCollector.php
* Copyright (C) 2016 Sander Dorigo
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Export\Collector;
use Auth;
use Crypt;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\ExportJob;
use Log;
/**
* Class AttachmentCollector
*
* @package FireflyIII\Export\Collector
*/
class AttachmentCollector extends BasicCollector implements CollectorInterface
{
/**
* AttachmentCollector constructor.
*
* @param ExportJob $job
*/
public function __construct(ExportJob $job)
{
parent::__construct($job);
}
public function run()
{
// grab all the users attachments:
$attachments = Auth::user()->attachments()->get();
Log::debug('Found ' . $attachments->count() . ' attachments.');
/** @var Attachment $attachment */
foreach ($attachments as $attachment) {
$originalFile = storage_path('upload') . DIRECTORY_SEPARATOR . 'at-' . $attachment->id . '.data';
if (file_exists($originalFile)) {
Log::debug('Stored 1 attachment');
$decrypted = Crypt::decrypt(file_get_contents($originalFile));
$newFile = storage_path('export') . DIRECTORY_SEPARATOR . $this->job->key . '-Attachment nr. ' . $attachment->id . ' - ' . $attachment->filename;
file_put_contents($newFile, $decrypted);
$this->getFiles()->push($newFile);
}
}
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* BasicCollector.php
* Copyright (C) 2016 Sander Dorigo
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Export\Collector;
use FireflyIII\Models\ExportJob;
use Illuminate\Support\Collection;
/**
* Class BasicCollector
*
* @package FireflyIII\Export\Collector
*/
class BasicCollector
{
/** @var Collection */
private $files;
/** @var ExportJob */
protected $job;
/**
* BasicCollector constructor.
*/
public function __construct(ExportJob $job)
{
$this->files = new Collection;
$this->job = $job;
}
/**
* @return Collection
*/
public function getFiles()
{
return $this->files;
}
/**
* @param Collection $files
*/
public function setFiles($files)
{
$this->files = $files;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* CollectorInterface.php
* Copyright (C) 2016 Sander Dorigo
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Export\Collector;
/**
* Interface CollectorInterface
*
* @package FireflyIII\Export\Collector
*/
interface CollectorInterface
{
public function run();
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* UploadCollector.php
* Copyright (C) 2016 Sander Dorigo
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Export\Collector;
use Auth;
use Crypt;
use FireflyIII\Models\ExportJob;
/**
* Class UploadCollector
*
* @package FireflyIII\Export\Collector
*/
class UploadCollector extends BasicCollector implements CollectorInterface
{
/**
* AttachmentCollector constructor.
*
* @param ExportJob $job
*/
public function __construct(ExportJob $job)
{
parent::__construct($job);
}
public function run()
{
// grab upload directory.
$path = storage_path('upload');
$files = scandir($path);
// only allow old uploads for this user:
$expected = 'csv-upload-' . Auth::user()->id . '-';
$len = strlen($expected);
foreach ($files as $entry) {
if (substr($entry, 0, $len) === $expected) {
// this is an original upload.
$parts = explode('-', str_replace(['.csv.encrypted', $expected], '', $entry));
$originalUpload = intval($parts[1]);
$date = date('Y-m-d \a\t H-i-s', $originalUpload);
$newFileName = 'Old CSV import dated ' . $date . '.csv';
$content = Crypt::decrypt(file_get_contents($path . DIRECTORY_SEPARATOR . $entry));
$fullPath = storage_path('export') . DIRECTORY_SEPARATOR . $this->job->key . '-' . $newFileName;
// write to file:
file_put_contents($fullPath, $content);
// add entry to set:
$this->getFiles()->push($fullPath);
}
}
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* ConfigurationFile.php
* Copyright (C) 2016 Sander Dorigo
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Export;
use FireflyIII\Models\ExportJob;
/**
* Class ConfigurationFile
*
* @package FireflyIII\Export
*/
class ConfigurationFile
{
/** @var ExportJob */
private $job;
public function __construct(ExportJob $job)
{
$this->job = $job;
}
/**
* @return bool
*/
public function make()
{
$fields = array_keys(get_class_vars(Entry::class));
$types = Entry::getTypes();
$configuration = [
'date-format' => 'Y-m-d', // unfortunately, this is hard-coded.
'has-headers' => true,
'map' => [], // we could build a map if necessary for easy re-import.
'roles' => [],
'mapped' => [],
'specifix' => [],
];
foreach ($fields as $field) {
$configuration['roles'][] = $types[$field];
}
$file = storage_path('export') . DIRECTORY_SEPARATOR . $this->job->key . '-configuration.json';
file_put_contents($file, json_encode($configuration, JSON_PRETTY_PRINT));
return $file;
}
}

120
app/Export/Entry.php Normal file
View File

@@ -0,0 +1,120 @@
<?php
/**
* Entry.php
* Copyright (C) 2016 Sander Dorigo
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Export;
use FireflyIII\Models\TransactionJournal;
/**
* To extend the exported object, in case of new features in Firefly III for example,
* do the following:
*
* - Add the field(s) to this class
* - Make sure the "fromJournal"-routine fills these fields.
* - Add them to the static function that returns its type (key=value. Remember that the only
* valid types can be found in config/csv.php (under "roles").
*
* These new entries should be should be strings and numbers as much as possible.
*
*
*
* Class Entry
*
* @package FireflyIII\Export
*/
class Entry
{
/** @var string */
public $amount;
/** @var string */
public $date;
/** @var string */
public $description;
/**
* @param TransactionJournal $journal
*
* @return Entry
*/
public static function fromJournal(TransactionJournal $journal)
{
$entry = new self;
$entry->setDescription($journal->description);
$entry->setDate($journal->date->format('Y-m-d'));
$entry->setAmount($journal->amount);
return $entry;
}
/**
* @return array
*/
public static function getTypes()
{
// key = field name (see top of class)
// value = field type (see csv.php under 'roles')
return [
'amount' => 'amount',
'date' => 'date-transaction',
'description' => 'description',
];
}
/**
* @return string
*/
public function getAmount()
{
return $this->amount;
}
/**
* @param string $amount
*/
public function setAmount($amount)
{
$this->amount = $amount;
}
/**
* @return string
*/
public function getDate()
{
return $this->date;
}
/**
* @param string $date
*/
public function setDate($date)
{
$this->date = $date;
}
/**
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* @param string $description
*/
public function setDescription($description)
{
$this->description = $description;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* BasicExporter.php
* Copyright (C) 2016 Sander Dorigo
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Export\Exporter;
use FireflyIII\Models\ExportJob;
use Illuminate\Support\Collection;
/**
* Class BasicExporter
*
* @package FireflyIII\Export\Exporter
*/
class BasicExporter
{
private $entries;
/** @var ExportJob */
protected $job;
/**
* BasicExporter constructor.
*/
public function __construct(ExportJob $job)
{
$this->entries = new Collection;
$this->job = $job;
}
/**
* @return Collection
*/
public function getEntries()
{
return $this->entries;
}
/**
* @param Collection $entries
*/
public function setEntries(Collection $entries)
{
$this->entries = $entries;
}
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* CsvExporter.php
* Copyright (C) 2016 Sander Dorigo
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Export\Exporter;
use FireflyIII\Export\Entry;
use FireflyIII\Models\ExportJob;
use League\Csv\Writer;
use SplFileObject;
/**
* Class CsvExporter
*
* @package FireflyIII\Export\Exporter
*/
class CsvExporter extends BasicExporter implements ExporterInterface
{
/** @var string */
private $fileName;
/** @var resource */
private $handler;
/**
* CsvExporter constructor.
*/
public function __construct(ExportJob $job)
{
parent::__construct($job);
}
/**
* @return string
*/
public function getFileName()
{
return $this->fileName;
}
/**
*
*/
public function run()
{
// create temporary file:
$this->tempFile();
// create CSV writer:
$writer = Writer::createFromPath(new SplFileObject($this->fileName, 'a+'), 'w');
//the $writer object open mode will be 'w'!!
// all rows:
$rows = [];
// add header:
$first = $this->getEntries()->first();
$rows[] = array_keys(get_object_vars($first));
// then the rest:
/** @var Entry $entry */
foreach ($this->getEntries() as $entry) {
$rows[] = array_values(get_object_vars($entry));
}
$writer->insertAll($rows);
}
private function tempFile()
{
$fileName = $this->job->key . '-records.csv';
$this->fileName = storage_path('export') . DIRECTORY_SEPARATOR . $fileName;
$this->handler = fopen($this->fileName, 'w');
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* ExporterInterface.php
* Copyright (C) 2016 Sander Dorigo
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Export\Exporter;
use Illuminate\Support\Collection;
/**
* Interface ExporterInterface
*
* @package FireflyIII\Export\Exporter
*/
interface ExporterInterface
{
/**
* @return Collection
*/
public function getEntries();
/**
*
*/
public function run();
/**
* @param Collection $entries
*
*/
public function setEntries(Collection $entries);
/**
* @return string
*/
public function getFileName();
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* JournalCollector.php
* Copyright (C) 2016 Sander Dorigo
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Export;
use Carbon\Carbon;
use FireflyIII\User;
use Illuminate\Support\Collection;
/**
* Class JournalCollector
*
* @package FireflyIII\Export\Collector
*/
class JournalCollector
{
/** @var Collection */
private $accounts;
/** @var Carbon */
private $end;
/** @var Carbon */
private $start;
/** @var User */
private $user;
/**
* JournalCollector constructor.
*
* @param Collection $accounts
* @param User $user
* @param Carbon $start
* @param Carbon $end
*/
public function __construct(Collection $accounts, User $user, Carbon $start, Carbon $end)
{
$this->accounts = $accounts;
$this->user = $user;
$this->start = $start;
$this->end = $end;
}
/**
* @return Collection
*/
public function collect()
{
// get all the journals:
$ids = $this->accounts->pluck('id')->toArray();
return $this->user->transactionjournals()
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->whereIn('transactions.account_id', $ids)
->before($this->end)
->after($this->start)
->orderBy('transaction_journals.date')
->get(['transaction_journals.*']);
}
}

154
app/Export/Processor.php Normal file
View File

@@ -0,0 +1,154 @@
<?php
/**
* Processor.php
* Copyright (C) 2016 Sander Dorigo
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Export;
use Auth;
use Config;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ExportJob;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Support\Collection;
use ZipArchive;
/**
* Class Processor
*
* @package FireflyIII\Export
*/
class Processor
{
/** @var Collection */
public $accounts;
/** @var string */
public $exportFormat;
/** @var bool */
public $includeAttachments;
/** @var bool */
public $includeConfig;
/** @var bool */
public $includeOldUploads;
/** @var ExportJob */
public $job;
/** @var array */
public $settings;
/** @var \FireflyIII\Export\ConfigurationFile */
private $configurationMaker;
/** @var Collection */
private $exportEntries;
/** @var Collection */
private $files;
/** @var Collection */
private $journals;
/**
* Processor constructor.
*
* @param array $settings
*/
public function __construct(array $settings)
{
// save settings
$this->settings = $settings;
$this->accounts = $settings['accounts'];
$this->exportFormat = $settings['exportFormat'];
$this->includeAttachments = $settings['includeAttachments'];
$this->includeConfig = $settings['includeConfig'];
$this->includeOldUploads = $settings['includeOldUploads'];
$this->job = $settings['job'];
$this->journals = new Collection;
$this->exportEntries = new Collection;
$this->files = new Collection;
}
/**
*
*/
public function collectAttachments()
{
$attachmentCollector = app('FireflyIII\Export\Collector\AttachmentCollector', [$this->job]);
$attachmentCollector->run();
$this->files = $this->files->merge($attachmentCollector->getFiles());
}
/**
*
*/
public function collectJournals()
{
$args = [$this->accounts, Auth::user(), $this->settings['startDate'], $this->settings['endDate']];
$journalCollector = app('FireflyIII\Export\JournalCollector', $args);
$this->journals = $journalCollector->collect();
}
public function collectOldUploads()
{
$uploadCollector = app('FireflyIII\Export\Collector\UploadCollector', [$this->job]);
$uploadCollector->run();
$this->files = $this->files->merge($uploadCollector->getFiles());
}
/**
*
*/
public function convertJournals()
{
/** @var TransactionJournal $journal */
foreach ($this->journals as $journal) {
$this->exportEntries->push(Entry::fromJournal($journal));
}
}
public function createConfigFile()
{
$this->configurationMaker = app('FireflyIII\Export\ConfigurationFile', [$this->job]);
$this->files->push($this->configurationMaker->make());
}
public function createZipFile()
{
$zip = new ZipArchive;
$filename = storage_path('export') . DIRECTORY_SEPARATOR . $this->job->key . '.zip';
if ($zip->open($filename, ZipArchive::CREATE) !== true) {
throw new FireflyException('Cannot store zip file.');
}
// for each file in the collection, add it to the zip file.
$search = storage_path('export') . DIRECTORY_SEPARATOR . $this->job->key . '-';
/** @var string $file */
foreach ($this->getFiles() as $file) {
$zipName = str_replace($search, '', $file);
$zip->addFile($file, $zipName);
}
$zip->close();
}
/**
*
*/
public function exportJournals()
{
$exporterClass = Config::get('firefly.export_formats.' . $this->exportFormat);
$exporter = app($exporterClass, [$this->job]);
$exporter->setEntries($this->exportEntries);
$exporter->run();
$this->files->push($exporter->getFileName());
}
/**
* @return Collection
*/
public function getFiles()
{
return $this->files;
}
}