Can configure file upload in file imports.

This commit is contained in:
James Cole 2018-05-06 07:09:08 +02:00
parent f74b9ba7ab
commit 7d80ac37a6
19 changed files with 791 additions and 205 deletions

View File

@ -29,6 +29,8 @@ use FireflyIII\Import\JobConfiguration\JobConfigurationInterface;
use FireflyIII\Models\ImportJob; use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\MessageBag;
use Log; use Log;
/** /**
@ -139,8 +141,17 @@ class JobConfigurationController extends Controller
return redirect(route('import.job.status.index', [$importJob->key])); return redirect(route('import.job.status.index', [$importJob->key]));
} }
// uploaded files are attached to the job.
// the configurator can then handle them.
$result = new MessageBag;
/** @var UploadedFile $upload */
foreach ($request->allFiles() as $name => $upload) {
$result = $this->repository->storeFileUpload($importJob, $name, $upload);
}
$data = $request->all(); $data = $request->all();
$messages = $configurator->configureJob($data); $messages = $configurator->configureJob($data);
$result->merge($messages);
if ($messages->count() > 0) { if ($messages->count() > 0) {
$request->session()->flash('warning', $messages->first()); $request->session()->flash('warning', $messages->first());

View File

@ -23,11 +23,20 @@ declare(strict_types=1);
namespace FireflyIII\Import\JobConfiguration; namespace FireflyIII\Import\JobConfiguration;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob; use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Support\Import\Configuration\File\ConfigurationInterface;
use FireflyIII\Support\Import\Configuration\File\ConfigureUploadHandler;
use FireflyIII\Support\Import\Configuration\File\NewFileJobHandler;
use Illuminate\Support\MessageBag; use Illuminate\Support\MessageBag;
class FileJobConfiguration implements JobConfigurationInterface class FileJobConfiguration implements JobConfigurationInterface
{ {
/** @var ImportJob */
private $importJob;
/** @var ImportJobRepositoryInterface */
private $repository;
/** /**
* ConfiguratorInterface constructor. * ConfiguratorInterface constructor.
@ -43,30 +52,86 @@ class FileJobConfiguration implements JobConfigurationInterface
* @param array $data * @param array $data
* *
* @return MessageBag * @return MessageBag
* @throws FireflyException
*/ */
public function configureJob(array $data): MessageBag public function configureJob(array $data): MessageBag
{ {
// TODO: Implement configureJob() method. $configurator = $this->getConfigurationObject();
$configurator->setJob($this->importJob);
return $configurator->configureJob($data);
} }
/** /**
* Return the data required for the next step in the job configuration. * Return the data required for the next step in the job configuration.
* *
* @throws FireflyException
* @return array * @return array
*/ */
public function getNextData(): array public function getNextData(): array
{ {
// TODO: Implement getNextData() method. $configurator = $this->getConfigurationObject();
$configurator->setJob($this->importJob);
return $configurator->getNextData();
}
/**
* Get the configuration handler for this specific stage.
*
* @return ConfigurationInterface
* @throws FireflyException
*/
private function getConfigurationObject(): ConfigurationInterface
{
$class = 'DoNotExist';
switch ($this->importJob->stage) {
case 'new': // has nothing, no file upload or anything.
$class = NewFileJobHandler::class;
break;
case 'configure-upload':
$class = ConfigureUploadHandler::class;
break;
// case 'upload-config': // has file, needs file config.
// $class = UploadConfig::class;
// break;
// case 'roles': // has configured file, needs roles.
// $class = Roles::class;
// break;
// case 'map': // has roles, needs mapping.
// $class = Map::class;
// break;
// default:
// break;
}
if (!class_exists($class)) {
throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class)); // @codeCoverageIgnore
}
return app($class);
} }
/** /**
* Returns the view of the next step in the job configuration. * Returns the view of the next step in the job configuration.
* *
* @throws FireflyException
* @return string * @return string
*/ */
public function getNextView(): string public function getNextView(): string
{ {
// TODO: Implement getNextView() method. switch ($this->importJob->stage) {
case 'new':
return 'import.file.new';
case 'configure-upload':
return 'import.file.configure-upload';
break;
default:
// @codeCoverageIgnoreStart
throw new FireflyException(
sprintf('FileJobConfiguration::getNextView() cannot handle stage "%s"', $this->importJob->stage)
);
// @codeCoverageIgnoreEnd
}
} }
/** /**
@ -76,7 +141,11 @@ class FileJobConfiguration implements JobConfigurationInterface
*/ */
public function configurationComplete(): bool public function configurationComplete(): bool
{ {
// TODO: Implement configurationComplete() method. if ($this->importJob->stage === 'ready_to run') {
return true;
}
return false;
} }
/** /**
@ -84,6 +153,8 @@ class FileJobConfiguration implements JobConfigurationInterface
*/ */
public function setJob(ImportJob $job): void public function setJob(ImportJob $job): void
{ {
// TODO: Implement setJob() method. $this->importJob = $job;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
} }
} }

View File

@ -41,7 +41,7 @@ class AbnAmroDescription implements SpecificInterface
*/ */
public static function getDescription(): string public static function getDescription(): string
{ {
return 'Fixes possible problems with ABN Amro descriptions.'; return 'import.specific_abn_descr';
} }
/** /**
@ -50,7 +50,7 @@ class AbnAmroDescription implements SpecificInterface
*/ */
public static function getName(): string public static function getName(): string
{ {
return 'ABN Amro description'; return 'import.specific_abn_name';
} }
/** /**

View File

@ -43,7 +43,7 @@ class IngDescription implements SpecificInterface
*/ */
public static function getDescription(): string public static function getDescription(): string
{ {
return 'Create better descriptions in ING import files.'; return 'import.specific_ing_descr';
} }
/** /**
@ -52,7 +52,7 @@ class IngDescription implements SpecificInterface
*/ */
public static function getName(): string public static function getName(): string
{ {
return 'ING description'; return 'import.specific_ing_name';
} }
/** /**

View File

@ -33,7 +33,7 @@ class PresidentsChoice implements SpecificInterface
*/ */
public static function getDescription(): string public static function getDescription(): string
{ {
return 'Fixes problems with files from Presidents Choice Financial.'; return 'import.specific_pres_descr';
} }
/** /**
@ -42,7 +42,7 @@ class PresidentsChoice implements SpecificInterface
*/ */
public static function getName(): string public static function getName(): string
{ {
return 'Presidents "Choice"'; return 'import.specific_pres_name';
} }
/** /**

View File

@ -35,7 +35,7 @@ class RabobankDescription implements SpecificInterface
*/ */
public static function getDescription(): string public static function getDescription(): string
{ {
return 'Fixes possible problems with Rabobank descriptions.'; return 'import.specific_pres_descr';
} }
/** /**
@ -44,7 +44,7 @@ class RabobankDescription implements SpecificInterface
*/ */
public static function getName(): string public static function getName(): string
{ {
return 'Rabobank description'; return 'import.specific_rabo_name';
} }
/** /**

View File

@ -33,7 +33,7 @@ class SnsDescription implements SpecificInterface
*/ */
public static function getDescription(): string public static function getDescription(): string
{ {
return 'Trim quotes from SNS descriptions.'; return 'import.specific_sns_descr';
} }
/** /**
@ -42,7 +42,7 @@ class SnsDescription implements SpecificInterface
*/ */
public static function getName(): string public static function getName(): string
{ {
return 'SNS description'; return 'import.specific_sns_name';
} }
/** /**

View File

@ -52,6 +52,15 @@ class ImportJob extends Model
/** @var array */ /** @var array */
protected $fillable = ['key', 'user_id', 'file_type', 'provider', 'status', 'stage', 'configuration', 'extended_status', 'transactions', 'errors']; protected $fillable = ['key', 'user_id', 'file_type', 'provider', 'status', 'stage', 'configuration', 'extended_status', 'transactions', 'errors'];
/**
* @codeCoverageIgnore
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function attachments()
{
return $this->morphMany(Attachment::class, 'attachable');
}
/** /**
* @param $value * @param $value
* *

View File

@ -24,11 +24,13 @@ namespace FireflyIII\Repositories\ImportJob;
use Crypt; use Crypt;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\ImportJob; use FireflyIII\Models\ImportJob;
use FireflyIII\Models\Tag; use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionJournalMeta; use FireflyIII\Models\TransactionJournalMeta;
use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\MessageBag;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Log; use Log;
use SplFileObject; use SplFileObject;
@ -42,6 +44,16 @@ class ImportJobRepository implements ImportJobRepositoryInterface
{ {
/** @var User */ /** @var User */
private $user; private $user;
/** @var int */
private $maxUploadSize;
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
protected $uploadDisk;
public function __construct()
{
$this->maxUploadSize = (int)config('firefly.maxUploadSize');
$this->uploadDisk = Storage::disk('upload');
}
/** /**
* @param ImportJob $job * @param ImportJob $job
@ -421,6 +433,8 @@ class ImportJobRepository implements ImportJobRepositoryInterface
/** /**
* Return import file content. * Return import file content.
* *
* @deprecated
*
* @param ImportJob $job * @param ImportJob $job
* *
* @return string * @return string
@ -476,4 +490,74 @@ class ImportJobRepository implements ImportJobRepositoryInterface
return $job; return $job;
} }
/**
* @codeCoverageIgnore
*
* @param UploadedFile $file
*
* @return bool
*/
protected function validSize(UploadedFile $file): bool
{
$size = $file->getSize();
return $size > $this->maxUploadSize;
}
/**
* Handle upload for job.
*
* @param ImportJob $job
* @param string $name
* @param UploadedFile $file
*
* @return MessageBag
* @throws FireflyException
*/
public function storeFileUpload(ImportJob $job, string $name, UploadedFile $file): MessageBag
{
$messages = new MessageBag;
if ($this->validSize($file)) {
$name = e($file->getClientOriginalName());
$messages->add('size', (string)trans('validation.file_too_large', ['name' => $name]));
return $messages;
}
$count = $job->attachments()->get()->filter(
function (Attachment $att) use($name) {
return $att->filename === $name;
}
)->count();
if ($count > 0) {
// don't upload, but also don't complain about it.
Log::error(sprintf('Detected duplicate upload. Will ignore second "%s" file.', $name));
return new MessageBag;
}
$attachment = new Attachment; // create Attachment object.
$attachment->user()->associate($job->user);
$attachment->attachable()->associate($job);
$attachment->md5 = md5_file($file->getRealPath());
$attachment->filename = $name;
$attachment->mime = $file->getMimeType();
$attachment->size = $file->getSize();
$attachment->uploaded = 0;
$attachment->save();
$fileObject = $file->openFile('r');
$fileObject->rewind();
$content = $fileObject->fread($file->getSize());
$encrypted = Crypt::encrypt($content);
// store it:
$this->uploadDisk->put($attachment->fileName(), $encrypted);
$attachment->uploaded = 1; // update attachment
$attachment->save();
// return it.
return new MessageBag;
}
} }

View File

@ -22,9 +22,11 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\ImportJob; namespace FireflyIII\Repositories\ImportJob;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob; use FireflyIII\Models\ImportJob;
use FireflyIII\Models\Tag; use FireflyIII\Models\Tag;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\MessageBag;
use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\File\UploadedFile;
/** /**
@ -33,6 +35,18 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
interface ImportJobRepositoryInterface interface ImportJobRepositoryInterface
{ {
/**
* Handle upload for job.
*
* @param ImportJob $job
* @param string $name
* @param UploadedFile $file
*
* @return MessageBag
* @throws FireflyException
*/
public function storeFileUpload(ImportJob $job, string $name, UploadedFile $file): MessageBag;
/** /**
* @param ImportJob $job * @param ImportJob $job
* @param array $transactions * @param array $transactions

View File

@ -145,16 +145,6 @@ class ExpandedForm
*/ */
public function assetAccountList(string $name, $value = null, array $options = []): string public function assetAccountList(string $name, $value = null, array $options = []): string
{ {
// properties for cache
$cache = new CacheProperties;
$cache->addProperty('exp-form-asset-list');
$cache->addProperty($name);
$cache->addProperty($value);
$cache->addProperty($options);
if ($cache->has()) {
return $cache->get();
}
// make repositories // make repositories
/** @var AccountRepositoryInterface $repository */ /** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
@ -182,7 +172,6 @@ class ExpandedForm
$grouped[$key][$account->id] = $account->name . ' (' . app('amount')->formatAnything($currency, $balance, false) . ')'; $grouped[$key][$account->id] = $account->name . ' (' . app('amount')->formatAnything($currency, $balance, false) . ')';
} }
$res = $this->select($name, $grouped, $value, $options); $res = $this->select($name, $grouped, $value, $options);
$cache->store($res);
return $res; return $res;
} }

View File

@ -20,12 +20,12 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Support\Import\Configuration; namespace FireflyIII\Support\Import\Configuration\File;
use FireflyIII\Models\ImportJob; use FireflyIII\Models\ImportJob;
use Illuminate\Support\MessageBag;
/** /**
* @deprecated
* Class ConfigurationInterface. * Class ConfigurationInterface.
*/ */
interface ConfigurationInterface interface ConfigurationInterface
@ -35,14 +35,7 @@ interface ConfigurationInterface
* *
* @return array * @return array
*/ */
public function getData(): array; public function getNextData(): array;
/**
* Return possible warning to user.
*
* @return string
*/
public function getWarningMessage(): string;
/** /**
* @param ImportJob $job * @param ImportJob $job
@ -52,11 +45,11 @@ interface ConfigurationInterface
public function setJob(ImportJob $job); public function setJob(ImportJob $job);
/** /**
* Store the result. * Store data associated with current stage.
* *
* @param array $data * @param array $data
* *
* @return bool * @return MessageBag
*/ */
public function storeConfiguration(array $data): bool; public function configureJob(array $data): MessageBag;
} }

View File

@ -0,0 +1,167 @@
<?php
/**
* ConfigureUploadHandlerphp
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Import\Configuration\File;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use Illuminate\Support\MessageBag;
use Log;
/**
* Class ConfigureUploadHandler
*
* @package FireflyIII\Support\Import\Configuration\File
*/
class ConfigureUploadHandler implements ConfigurationInterface
{
/** @var ImportJob */
private $importJob;
/** @var ImportJobRepositoryInterface */
private $repository;
/** @var AccountRepositoryInterface */
private $accountRepos;
/**
* Get the data necessary to show the configuration screen.
*
* @return array
*/
public function getNextData(): array
{
$delimiters = [
',' => trans('form.csv_comma'),
';' => trans('form.csv_semicolon'),
'tab' => trans('form.csv_tab'),
];
$config = $this->importJob->configuration;
$config['date-format'] = $config['date-format'] ?? 'Ymd';
$specifics = [];
$this->repository->setConfiguration($this->importJob, $config);
// collect specifics.
foreach (config('csv.import_specifics') as $name => $className) {
$specifics[$name] = [
'name' => $className::getName(),
'description' => $className::getDescription(),
];
}
$data = [
'accounts' => [],
'specifix' => [],
'delimiters' => $delimiters,
'specifics' => $specifics,
];
return $data;
}
/**
* @param ImportJob $job
*
* @return ConfigurationInterface
*/
public function setJob(ImportJob $job)
{
$this->importJob = $job;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->accountRepos->setUser($job->user);
}
/**
* Store data associated with current stage.
*
* @param array $data
*
* @return MessageBag
*/
public function configureJob(array $data): MessageBag
{
$config = $this->importJob->configuration;
$complete = true;
// collect values:
$importId = isset($data['csv_import_account']) ? (int)$data['csv_import_account'] : 0;
$delimiter = (string)$data['csv_delimiter'];
$config['has-headers'] = (int)($data['has_headers'] ?? 0.0) === 1;
$config['date-format'] = (string)$data['date_format'];
$config['delimiter'] = 'tab' === $delimiter ? "\t" : $delimiter;
$config['apply-rules'] = (int)($data['apply_rules'] ?? 0.0) === 1;
$config['specifics'] = $this->getSpecifics($data);
// validate values:
$account = $this->accountRepos->findNull($importId);
// respond to invalid account:
if (null === $account) {
Log::error('Could not find anything for csv_import_account.', ['id' => $importId]);
$complete = false;
}
if (null !== $account) {
$config['import-account'] = $account->id;
}
$this->repository->setConfiguration($this->importJob, $config);
if ($complete) {
$this->repository->setStage($this->importJob, 'roles');
}
if (!$complete) {
$messages = new MessageBag;
$messages->add('account', trans('import.invalid_import_account'));
return $messages;
}
return new MessageBag;
}
/**
* @param array $data
*
* @return array
*/
private function getSpecifics(array $data): array
{
$return = [];
// check if specifics given are correct:
if (isset($data['specifics']) && \is_array($data['specifics'])) {
foreach ($data['specifics'] as $name) {
// verify their content.
$className = sprintf('FireflyIII\Import\Specifics\%s', $name);
if (class_exists($className)) {
$return[$name] = 1;
}
}
}
return $return;
}
}

View File

@ -0,0 +1,176 @@
<?php
/**
* NewFileJobHandler.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Import\Configuration\File;
use Crypt;
use FireflyIII\Console\Commands\Import;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Support\MessageBag;
use Log;
use Storage;
use Exception;
/**
* Class NewFileJobHandler
*
* @package FireflyIII\Support\Import\Configuration\File
*/
class NewFileJobHandler implements ConfigurationInterface
{
/** @var ImportJob */
private $importJob;
/** @var ImportJobRepositoryInterface */
private $repository;
/**
* Get the data necessary to show the configuration screen.
*
* @return array
*/
public function getNextData(): array
{
$importFileTypes = [];
$defaultImportType = config('import.options.file.default_import_format');
foreach (config('import.options.file.import_formats') as $type) {
$importFileTypes[$type] = trans('import.import_file_type_' . $type);
}
return [
'default_type' => $defaultImportType,
'file_types' => $importFileTypes,
];
}
/**
* @param ImportJob $job
*/
public function setJob(ImportJob $job): void
{
$this->importJob = $job;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
}
/**
* Store data associated with current stage.
*
* @param array $data
*
* @throws FireflyException
* @return MessageBag
*/
public function configureJob(array $data): MessageBag
{
// nothing to store, validate upload
// and push to next stage.
$messages = new MessageBag;
$attachments = $this->importJob->attachments;
/** @var Attachment $attachment */
foreach ($attachments as $attachment) {
// check if content is UTF8:
if (!$this->isUTF8($attachment)) {
$message = trans('import.file_not_utf8');
Log::error($message);
$messages->add('import_file', $message);
// delete attachment:
try {
$attachment->delete();
} catch (Exception $e) {
throw new FireflyException(sprintf('Could not delete attachment: %s', $e->getMessage()));
}
return $messages;
}
// if file is configuration file, store it into the job.
if ($attachment->filename === 'configuration_file') {
$this->storeConfig($attachment);
}
}
$this->repository->setStage($this->importJob, 'configure-upload');
return new MessageBag();
}
/**
* @param Attachment $attachment
*
* @return bool
* @throws FireflyException
*/
private function isUTF8(Attachment $attachment): bool
{
$disk = Storage::disk('upload');
try {
$content = $disk->get(sprintf('at-%d.data', $attachment->id));
$content = Crypt::decrypt($content);
} catch (FileNotFoundException|DecryptException $e) {
Log::error($e->getMessage());
throw new FireflyException($e->getMessage());
}
$result = mb_detect_encoding($content, 'UTF-8', true);
if ($result === false) {
return false;
}
if ($result !== 'ASCII' && $result !== 'UTF-8') {
return false;
}
return true;
}
/**
* @param Attachment $attachment
*
* @throws FireflyException
*/
private function storeConfig(Attachment $attachment): void
{
$disk = Storage::disk('upload');
try {
$content = $disk->get(sprintf('at-%d.data', $attachment->id));
$content = Crypt::decrypt($content);
} catch (FileNotFoundException $e) {
Log::error($e->getMessage());
throw new FireflyException($e->getMessage());
}
$json = json_decode($content, true);
if (null !== $json) {
$this->repository->setConfiguration($this->importJob, $json);
}
}
}

View File

@ -0,0 +1,20 @@
$(function () {
"use strict";
var importMultiSelect = {
disableIfEmpty: true,
selectAllText: selectAllText,
nonSelectedText: nonSelectedText,
nSelectedText: nSelectedText,
allSelectedText: allSelectedText,
includeSelectAllOption: true,
enableFiltering: true,
enableCaseInsensitiveFiltering: true,
filterPlaceholder: filterPlaceholder,
enableHTML: true,
};
// make account select a hip new bootstrap multi-select thing.
$('#inputSpecifics').multiselect(importMultiSelect);
});

View File

@ -80,6 +80,39 @@ return [
'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.',
'job_config_fake_album_title' => 'Enter album name', 'job_config_fake_album_title' => 'Enter album name',
'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.',
// job configuration form the file provider
'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file',
'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ',
'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.',
'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their <a href="https://github.com/firefly-iii/import-configurations/wiki">configuration file</a>',
'job_config_file_upload_type_help' => 'Select the type of file you will upload',
'job_config_file_upload_submit' => 'Upload files',
'import_file_type_csv' => 'CSV (comma separated values)',
'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.',
'job_config_uc_title' => 'Import setup (2/4) - Basic file setup',
'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.',
'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.',
'job_config_uc_date_help' => 'Date time format in your file. Follow the format as <a href="https://secure.php.net/manual/en/datetime.createfromformat.php#refsect1-datetime.createfromformat-parameters">this page</a> indicates. The default value will parse dates that look like this: :dateExample.',
'job_config_uc_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.',
'job_config_uc_account_help' => 'If your file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the file belong to.',
'job_config_uc_apply_rules_title' => 'Apply rules',
'job_config_uc_apply_rules_text' => 'Applies your rules to every imported transaction. Note that this slows the import significantly.',
'job_config_uc_specifics_title' => 'Bank-specific options',
'job_config_uc_specifics_txt' => 'Some banks deliver badly formatted files. Firefly III can fix those automatically. If your bank delivers such files, open an issue on GitHub.',
'job_config_uc_submit' => 'Continue',
'invalid_import_account' => 'You have selected an invalid account to import into.',
// specifics:
'specific_ing_name' => 'ING NL',
'specific_ing_descr' => 'Create better descriptions in ING exports',
'specific_sns_name' => 'SNS / Volksbank NL',
'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files',
'specific_abn_name' => 'ABN AMRO NL',
'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files',
'specific_rabo_name' => 'Rabobank NL',
'specific_rabo_descr' => 'Fixes potential problems with Rabobank files',
'specific_pres_name' => 'President\'s Choice Financial CA',
'specific_pres_descr' => 'Fixes potential problems with PC files',
// import status page: // import status page:
'import_with_key' => 'Import with key \':key\'', 'import_with_key' => 'Import with key \':key\'',

View File

@ -0,0 +1,115 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.render(Route.getCurrentRoute.getName, importJob) }}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('import.job_config_uc_title') }}</h3>
</div>
<div class="box-body">
<p>
{{ trans('import.job_config_uc_text') }}
</p>
</div>
</div>
</div>
</div>
<form class="form-horizontal" action="{{ route('import.job.configuration.post', importJob.key) }}" method="post" enctype="multipart/form-data">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<div class="row">
<div class="col-lg-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('import.job_config_input') }}</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-6">
<h4>{{ 'mandatoryFields'|_ }}</h4>
{{ ExpandedForm.checkbox('has_headers',1,importJob.configuration['has-headers'],{helpText: trans('import.job_config_uc_header_help')}) }}
{{ ExpandedForm.text('date_format',importJob.configuration['date-format'],{helpText: trans('import.job_config_uc_date_help', {dateExample: phpdate('Ymd')}) }) }}
{{ ExpandedForm.select('csv_delimiter', data.delimiters, importJob.configuration['delimiter'], {helpText: trans('import.job_config_uc_delimiter_help') } ) }}
{{ ExpandedForm.assetAccountList('csv_import_account', importJob.configuration['import-account'], {helpText: trans('import.job_config_uc_account_help')}) }}
<h4>{{ 'optionalFields'|_ }}</h4>
<div class="form-group">
<label for="apply_rules_label" class="col-sm-4 control-label">
{{ trans('import.job_config_uc_apply_rules_title') }}
</label>
<div class="col-sm-8">
<div class="checkbox"><label>
{{ Form.checkbox('apply_rules', '1',
importJob.configuration['apply-rules'] == true, {'id': 'apply_rules_label'}) }}
{{ trans('import.job_config_uc_apply_rules_text') }}
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="inputSpecifics" class="col-sm-4 control-label">{{ trans('import.job_config_uc_specifics_title') }}</label>
<div class="col-sm-8">
<p>
{{ trans('import.job_config_uc_specifics_txt') }}
</p>
<select id="inputSpecifics" name="specifics[]" multiple class="form-control">
{% for type, specific in data.specifics %}
<option value="{{ type }}" {% if importJob.configuration.specifics[type] == 1 %}selected{% endif %}>
{{ trans(specific.name) }}
&lt;small&gt;&lt;br&gt;{{ trans(specific.description) }}&lt;/small&gt;
</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="box">
<div class="box-body">
<button type="submit" class="pull-right btn btn-success">
{{ trans('import.job_config_uc_submit') }}
</button>
</div>
</div>
</div>
</div>
</form>
<p>
<br/>&nbsp;<br/>&nbsp;<br/>&nbsp;<br/>&nbsp;<br/>&nbsp;<br/>&nbsp;<br/>
</p>
{% endblock %}
{% block styles %}
<link href="css/bootstrap-multiselect.css?v={{ FF_VERSION }}" rel="stylesheet" type="text/css"/>
{% endblock %}
{% block scripts %}
<script type="text/javascript">
var selectAllText = "{{ trans('firefly.multi_select_select_all')|escape('js') }}";
var nonSelectedText = "{{ trans('firefly.multi_select_no_selection')|escape('js') }}";
var nSelectedText = "{{ trans('firefly.multi_select_n_selected')|escape('js') }}";
var allSelectedText = "{{ trans('firefly.multi_select_all_selected')|escape('js') }}";
var filterPlaceholder = "{{ trans('firefly.multi_select_filter_placeholder')|escape('js') }}";
</script>
<script type="text/javascript" src="js/lib/bootstrap-multiselect.js?v={{ FF_VERSION }}"></script>
<script type="text/javascript" src="js/ff/import/file/configure-upload.js?v={{ FF_VERSION }}"></script>
{% endblock %}

View File

@ -8,11 +8,11 @@
<div class="col-lg-12"> <div class="col-lg-12">
<div class="box"> <div class="box">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title">{{ trans('import.file_upload_title') }}</h3> <h3 class="box-title">{{ trans('import.job_config_file_upload_title') }}</h3>
</div> </div>
<div class="box-body"> <div class="box-body">
<p> <p>
{{ trans('import.file_upload_text') }} {{ trans('import.job_config_file_upload_text') }}
</p> </p>
</div> </div>
</div> </div>
@ -28,12 +28,12 @@
<div class="col-lg-12"> <div class="col-lg-12">
<div class="box"> <div class="box">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title">{{ trans('import.file_upload_fields') }}</h3> <h3 class="box-title">{{ trans('import.job_config_input') }}</h3>
</div> </div>
<div class="box-body"> <div class="box-body">
{{ ExpandedForm.file('import_file', {helpText: trans('import.file_upload_help')}) }} {{ ExpandedForm.file('import_file', {helpText: trans('import.job_config_file_upload_help')}) }}
{{ ExpandedForm.file('configuration_file', {helpText: trans('import.file_upload_config_help')|raw}) }} {{ ExpandedForm.file('configuration_file', {helpText: trans('import.job_config_file_upload_config_help')|raw}) }}
{{ ExpandedForm.select('import_file_type', data.file_types, data.default_type, {'helpText' : trans('import.file_upload_type_help')}) }} {{ ExpandedForm.select('import_file_type', data.file_types, data.default_type, {'helpText' : trans('import.job_config_file_upload_type_help')}) }}
</div> </div>
</div> </div>
</div> </div>
@ -43,7 +43,7 @@
<div class="box"> <div class="box">
<div class="box-body"> <div class="box-body">
<button type="submit" class="btn btn-success pull-right"> <button type="submit" class="btn btn-success pull-right">
{{ trans('import.file_upload_submit') }} <i class="fa fa-arrow-right"></i> {{ trans('import.job_config_file_upload_submit') }} <i class="fa fa-arrow-right"></i>
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,96 +0,0 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.render(Route.getCurrentRoute.getName, job) }}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('import.csv_initial_title') }}</h3>
</div>
<div class="box-body">
<p>
{{ trans('import.csv_initial_text') }}
</p>
</div>
</div>
</div>
</div>
<form class="form-horizontal" action="{{ route('import.configure.post', job.key) }}" method="post" enctype="multipart/form-data">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<div class="row">
<div class="col-lg-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('import.csv_initial_box_title') }}</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-6">
<h4>{{ 'mandatoryFields'|_ }}</h4>
{{ ExpandedForm.checkbox('has_headers',1,job.configuration['has-headers'],{helpText: trans('import.csv_initial_header_help')}) }}
{{ ExpandedForm.text('date_format',job.configuration['date-format'],{helpText: trans('import.csv_initial_date_help', {dateExample: phpdate('Ymd')}) }) }}
{{ ExpandedForm.select('csv_delimiter', data.delimiters, job.configuration['delimiter'], {helpText: trans('import.csv_initial_delimiter_help') } ) }}
{{ ExpandedForm.select('csv_import_account', data.accounts, job.configuration['import-account'], {helpText: trans('import.csv_initial_import_account_help')} ) }}
<h4>{{ 'optionalFields'|_ }}</h4>
<div class="form-group">
<label for="apply_rules_label" class="col-sm-4 control-label">
{{ trans('import.file_apply_rules_title') }}
</label>
<div class="col-sm-8">
<div class="checkbox"><label>
{{ Form.checkbox('apply_rules', '1',
job.configuration['apply-rules'] == true, {'id': 'apply_rules_label'}) }}
{{ trans('import.file_apply_rules_description') }}
</label>
</div>
</div>
</div>
{% for type, specific in data.specifics %}
<div class="form-group">
<label for="{{ type }}_label" class="col-sm-4 control-label">
{{ specific.name }}
</label>
<div class="col-sm-8">
<div class="checkbox"><label>
{{ Form.checkbox('specifics['~type~']', '1',
job.configuration.specifics[type] == '1', {'id': type ~ '_label'}) }}
{{ specific.description }}
</label>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="box">
<div class="box-body">
<button type="submit" class="pull-right btn btn-success">
{{ trans('import.csv_initial_submit') }}
</button>
</div>
</div>
</div>
</div>
</form>
{% endblock %}