diff --git a/.env.example b/.env.example index 28d87ce566..87540a4f1c 100755 --- a/.env.example +++ b/.env.example @@ -40,6 +40,7 @@ SHOW_INCOMPLETE_TRANSLATIONS=false CACHE_PREFIX=firefly +GOOGLE_MAPS_API_KEY= ANALYTICS_ID= SITE_OWNER=mail@example.com @@ -48,4 +49,5 @@ PUSHER_SECRET= PUSHER_APP_ID= DEMO_USERNAME= -DEMO_PASSWORD= \ No newline at end of file +DEMO_PASSWORD= + diff --git a/.github/CONTRIBUTING b/.github/CONTRIBUTING new file mode 100644 index 0000000000..2a1338d94d --- /dev/null +++ b/.github/CONTRIBUTING @@ -0,0 +1 @@ +If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.github.io/requested-features/) first. Thanks! \ No newline at end of file diff --git a/.scrutinizer.yml b/.scrutinizer.yml index b2245b4a46..ac754cc957 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -2,7 +2,50 @@ tools: external_code_coverage: false filter: - excluded_paths: - - app/Support/Migration/* - - app/database/migrations/* - - database/migrations/* + paths: + - app/* + - public/js/ff/* + excluded_paths: + - "database/migrations/*" + - "bootstrap/*" + - "config/*" + - "docker/*" + - "public/js/lib/*" + - "public/lib/adminlte/js/*" + - "public/lib/bootstrap/js/*" + - "resources/*" + - "routes/*" + - "storage/*" +checks: + php: + use_self_instead_of_fqcn: true + uppercase_constants: true + return_doc_comments: true + return_doc_comment_if_not_inferrable: true + remove_extra_empty_lines: true + parameter_doc_comments: true + optional_parameters_at_the_end: true + no_short_variable_names: + minimum: '3' + no_short_method_names: + minimum: '3' + no_long_variable_names: + maximum: '20' + no_goto: true + newline_at_end_of_file: true + encourage_single_quotes: true + avoid_todo_comments: true + avoid_perl_style_comments: true + avoid_fixme_comments: true + avoid_multiple_statements_on_same_line: true + align_assignments: true + duplication: false + javascript: true + +coding_style: + php: + spaces: + around_operators: + concatenation: true + other: + after_type_cast: false \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c2d14ac49e..7a7d24c3a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.3.1] - 2017-01-04 +### Added +- Support for Russian and Polish. +- Support for a proper demo website. +- Support for custom decimal places in currencies (#506, suggested by @xpfgsyb). +- Most amounts are now right-aligned (#511, suggested by @xpfgsyb). +- German is now a "complete" language, more than 75% translated! + +### Changed +- **[New Github repository!](github.com/firefly-iii/firefly-iii)** +- Better category overview. +- #502, thanks to @zjean + +### Removed +- Removed a lot of administration functions. +- Removed ability to activate users. + +### Fixed +- #501, thanks to @zjean +- #513, thanks to @skibbipl + +### Security +- #519, thanks to @xpfgsyb + ## [4.3.0] - 2015-12-26 ### Added - New method of keeping track of available budget, see issue #489 @@ -279,7 +303,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Bug in the mass edit routines. -- Firefly III over a proxy will now work (see [issue #290](https://github.com/JC5/firefly-iii/issues/290)), thanks @dfiel for reporting. +- Firefly III over a proxy will now work (see [issue #290](https://github.com/firefly-iii/firefly-iii/issues/290)), thanks @dfiel for reporting. - Sneaky bug in the import routine, fixed by @Bonno ## [3.10.1] - 2016-08-25 diff --git a/README.md b/README.md index 11c105c0a6..5005beca67 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,11 @@ # Firefly III: A personal finances manager -[![Requires PHP7](https://img.shields.io/badge/php-7.0-red.svg)](https://secure.php.net/downloads.php#v7.0.4) [![Latest Stable Version](https://poser.pugx.org/grumpydictator/firefly-iii/v/stable)](https://packagist.org/packages/grumpydictator/firefly-iii) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/JC5/firefly-iii/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/JC5/firefly-iii/?branch=master) - -[![Build Status](https://travis-ci.org/JC5/firefly-iii.svg?branch=master)](https://travis-ci.org/JC5/firefly-iii) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) +[![Requires PHP7](https://img.shields.io/badge/php-7.0-red.svg)](https://secure.php.net/downloads.php#v7.0.4) [![Latest Stable Version](https://poser.pugx.org/grumpydictator/firefly-iii/v/stable)](https://packagist.org/packages/grumpydictator/firefly-iii) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/?branch=master) [![Build Status](https://travis-ci.org/firefly-iii/firefly-iii.svg?branch=master)](https://travis-ci.org/firefly-iii/firefly-iii) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) [![The index of Firefly III](https://i.nder.be/hurdhgyg/400)](https://i.nder.be/h2b37243) [![The account overview of Firefly III](https://i.nder.be/hnkfkdpr/400)](https://i.nder.be/hv70pbwc) [![The useful financial reports of Firefly III](https://i.nder.be/h7sk6nb7/400)](https://i.nder.be/ccn0u2mp) [![Even more useful reports in Firefly III](https://i.nder.be/g237hr35/400)](https://i.nder.be/gm8hbh7z) -_(You can click on the images for a better view)_ - "Firefly III" is a financial manager. It can help you keep track of expenses, income, budgets and everything in between. It even supports credit cards, shared household accounts and savings accounts! It's pretty fancy. You should use it to save and organise money. ## Try it out! diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index 07cebe2fdf..7b3e905b88 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -15,6 +15,8 @@ namespace FireflyIII\Console\Commands; use DB; +use FireflyIII\Models\BudgetLimit; +use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use Illuminate\Console\Command; @@ -57,8 +59,29 @@ class UpgradeDatabase extends Command public function handle() { $this->setTransactionIdentifier(); + $this->migrateRepetitions(); + } - + private function migrateRepetitions() + { + if (!Schema::hasTable('budget_limits')) { + return; + } + // get all budget limits with end_date NULL + $set = BudgetLimit::whereNull('end_date')->get(); + $this->line(sprintf('Found %d budget limit(s) to update', $set->count())); + /** @var BudgetLimit $budgetLimit */ + foreach ($set as $budgetLimit) { + // get limit repetition (should be just one): + /** @var LimitRepetition $repetition */ + $repetition = $budgetLimit->limitrepetitions()->first(); + if (!is_null($repetition)) { + $budgetLimit->end_date = $repetition->enddate; + $budgetLimit->save(); + $this->line(sprintf('Updated budget limit #%d', $budgetLimit->id)); + $repetition->delete(); + } + } } /** @@ -85,42 +108,52 @@ class UpgradeDatabase extends Command $journalIds = array_unique($result->pluck('id')->toArray()); foreach ($journalIds as $journalId) { - // grab all positive transactiosn from this journal that are not deleted. - // for each one, grab the negative opposing one which has 0 as an identifier and give it the same identifier. - $identifier = 0; - $processed = []; - $transactions = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->get(); - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - // find opposing: - $amount = bcmul(strval($transaction->amount), '-1'); + $this->updateJournal(intval($journalId)); + } + } - try { - /** @var Transaction $opposing */ - $opposing = Transaction::where('transaction_journal_id', $journalId) - ->where('amount', $amount)->where('identifier', '=', 0) - ->whereNotIn('id', $processed) - ->first(); - } catch (QueryException $e) { - Log::error($e->getMessage()); - $this->error('Firefly III could not find the "identifier" field in the "transactions" table.'); - $this->error(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version'))); - $this->error('Please run "php artisan migrate" to add this field to the table.'); - $this->info('Then, run "php artisan firefly:upgrade-database" to try again.'); - break 2; - } - if (!is_null($opposing)) { - // give both a new identifier: - $transaction->identifier = $identifier; - $transaction->save(); - $opposing->identifier = $identifier; - $opposing->save(); - $processed[] = $transaction->id; - $processed[] = $opposing->id; - $this->line(sprintf('Database upgrade for journal #%d, transactions #%d and #%d', $journalId, $transaction->id, $opposing->id)); - } - $identifier++; + /** + * grab all positive transactiosn from this journal that are not deleted. for each one, grab the negative opposing one + * which has 0 as an identifier and give it the same identifier. + * + * @param int $journalId + */ + private function updateJournal(int $journalId) + { + $identifier = 0; + $processed = []; + $transactions = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->get(); + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + // find opposing: + $amount = bcmul(strval($transaction->amount), '-1'); + + try { + /** @var Transaction $opposing */ + $opposing = Transaction::where('transaction_journal_id', $journalId) + ->where('amount', $amount)->where('identifier', '=', 0) + ->whereNotIn('id', $processed) + ->first(); + } catch (QueryException $e) { + Log::error($e->getMessage()); + $this->error('Firefly III could not find the "identifier" field in the "transactions" table.'); + $this->error(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version'))); + $this->error('Please run "php artisan migrate" to add this field to the table.'); + $this->info('Then, run "php artisan firefly:upgrade-database" to try again.'); + + return; } + if (!is_null($opposing)) { + // give both a new identifier: + $transaction->identifier = $identifier; + $transaction->save(); + $opposing->identifier = $identifier; + $opposing->save(); + $processed[] = $transaction->id; + $processed[] = $opposing->id; + $this->line(sprintf('Database upgrade for journal #%d, transactions #%d and #%d', $journalId, $transaction->id, $opposing->id)); + } + $identifier++; } } } diff --git a/app/Console/Commands/UpgradeFireflyInstructions.php b/app/Console/Commands/UpgradeFireflyInstructions.php index df007ab40f..866884de56 100644 --- a/app/Console/Commands/UpgradeFireflyInstructions.php +++ b/app/Console/Commands/UpgradeFireflyInstructions.php @@ -63,20 +63,21 @@ class UpgradeFireflyInstructions extends Command } - if (is_null($text)) { $this->line(sprintf('Thank you for installing Firefly III, v%s', $version)); $this->info('There are no extra upgrade instructions.'); $this->line('Firefly III should be ready for use.'); - } else { - $this->line('+------------------------------------------------------------------------------+'); - $this->line(''); - $this->line(sprintf('Thank you for installing Firefly III, v%s', $version)); - $this->info(wordwrap($text)); - $this->line(''); - $this->line('+------------------------------------------------------------------------------+'); + + return; } + $this->line('+------------------------------------------------------------------------------+'); + $this->line(''); + $this->line(sprintf('Thank you for installing Firefly III, v%s', $version)); + $this->info(wordwrap($text)); + $this->line(''); + $this->line('+------------------------------------------------------------------------------+'); + } } diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index 47b40ca69f..8c73968caa 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -130,10 +130,9 @@ class VerifyDatabase extends Command /** @var Budget $entry */ foreach ($set as $entry) { - $name = $entry->encrypted ? Crypt::decrypt($entry->name) : $entry->name; $line = sprintf( 'Notice: User #%d (%s) has budget #%d ("%s") which has no budget limits.', - $entry->user_id, $entry->email, $entry->id, $name + $entry->user_id, $entry->email, $entry->id, $entry->name ); $this->line($line); } @@ -174,10 +173,8 @@ class VerifyDatabase extends Command $configuration = [ // a withdrawal can not have revenue account: TransactionType::WITHDRAWAL => [AccountType::REVENUE], - // deposit cannot have an expense account: TransactionType::DEPOSIT => [AccountType::EXPENSE], - // transfer cannot have either: TransactionType::TRANSFER => [AccountType::EXPENSE, AccountType::REVENUE], ]; diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 7c9c52fc01..dc634190ca 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -21,7 +21,6 @@ use FireflyIII\Console\Commands\ScanAttachments; use FireflyIII\Console\Commands\UpgradeDatabase; use FireflyIII\Console\Commands\UpgradeFireflyInstructions; use FireflyIII\Console\Commands\VerifyDatabase; -use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; /** @@ -76,15 +75,4 @@ class Kernel extends ConsoleKernel { require base_path('routes/console.php'); } - - /** - * Define the application's command schedule. - * - * @param \Illuminate\Console\Scheduling\Schedule $schedule - * - * @return void - */ - protected function schedule(Schedule $schedule) - { - } } diff --git a/app/Events/BlockedBadLogin.php b/app/Events/BlockedBadLogin.php deleted file mode 100644 index fb984abd04..0000000000 --- a/app/Events/BlockedBadLogin.php +++ /dev/null @@ -1,41 +0,0 @@ -email = $email; - $this->ipAddress = $ipAddress; - } -} diff --git a/app/Events/BlockedUseOfDomain.php b/app/Events/BlockedUseOfDomain.php deleted file mode 100644 index 0ccacea749..0000000000 --- a/app/Events/BlockedUseOfDomain.php +++ /dev/null @@ -1,42 +0,0 @@ -email = $email; - $this->ipAddress = $ipAddress; - } -} diff --git a/app/Events/BlockedUseOfEmail.php b/app/Events/BlockedUseOfEmail.php deleted file mode 100644 index 8b9d7cbed5..0000000000 --- a/app/Events/BlockedUseOfEmail.php +++ /dev/null @@ -1,42 +0,0 @@ -email = $email; - $this->ipAddress = $ipAddress; - } -} diff --git a/app/Events/BlockedUserLogin.php b/app/Events/BlockedUserLogin.php deleted file mode 100644 index fd12fb8a36..0000000000 --- a/app/Events/BlockedUserLogin.php +++ /dev/null @@ -1,42 +0,0 @@ -user = $user; - $this->ipAddress = $ipAddress; - } -} diff --git a/app/Events/ConfirmedUser.php b/app/Events/ConfirmedUser.php deleted file mode 100644 index 20fe797488..0000000000 --- a/app/Events/ConfirmedUser.php +++ /dev/null @@ -1,42 +0,0 @@ -user = $user; - $this->ipAddress = $ipAddress; - } -} diff --git a/app/Events/DeletedUser.php b/app/Events/DeletedUser.php deleted file mode 100644 index 80ab5d58ce..0000000000 --- a/app/Events/DeletedUser.php +++ /dev/null @@ -1,38 +0,0 @@ -email = $email; - } -} diff --git a/app/Events/LockedOutUser.php b/app/Events/LockedOutUser.php deleted file mode 100644 index c498ad1ed7..0000000000 --- a/app/Events/LockedOutUser.php +++ /dev/null @@ -1,41 +0,0 @@ -email = $email; - $this->ipAddress = $ipAddress; - } -} diff --git a/app/Events/ResentConfirmation.php b/app/Events/ResentConfirmation.php deleted file mode 100644 index 1ce53d51b3..0000000000 --- a/app/Events/ResentConfirmation.php +++ /dev/null @@ -1,42 +0,0 @@ -user = $user; - $this->ipAddress = $ipAddress; - } -} diff --git a/app/Events/StoredBudgetLimit.php b/app/Events/StoredBudgetLimit.php deleted file mode 100644 index 65b7be0008..0000000000 --- a/app/Events/StoredBudgetLimit.php +++ /dev/null @@ -1,50 +0,0 @@ -budgetLimit = $budgetLimit; - $this->end = $end; - - } - -} diff --git a/app/Events/UpdatedBudgetLimit.php b/app/Events/UpdatedBudgetLimit.php deleted file mode 100644 index 3642531e75..0000000000 --- a/app/Events/UpdatedBudgetLimit.php +++ /dev/null @@ -1,50 +0,0 @@ -budgetLimit = $budgetLimit; - $this->end = $end; - - } - -} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index c8b15f984d..c61dac5629 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -21,6 +21,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Session\TokenMismatchException; use Illuminate\Validation\ValidationException as ValException; +use Request; use Symfony\Component\HttpKernel\Exception\HttpException; /** @@ -72,6 +73,7 @@ class Handler extends ExceptionHandler * This is a great spot to send exceptions to Sentry, Bugsnag, etc. * * @param Exception $exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. * * @return void */ @@ -98,8 +100,8 @@ class Handler extends ExceptionHandler ]; // create job that will mail. - $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; - $job = new MailError($userData, env('SITE_OWNER', ''), $ip, $data); + $ipAddress = Request::ip() ?? '0.0.0.0'; + $job = new MailError($userData, env('SITE_OWNER', ''), $ipAddress, $data); dispatch($job); } diff --git a/app/Export/Collector/CollectorInterface.php b/app/Export/Collector/CollectorInterface.php index 6fc24233fe..f1778e9adc 100644 --- a/app/Export/Collector/CollectorInterface.php +++ b/app/Export/Collector/CollectorInterface.php @@ -35,6 +35,8 @@ interface CollectorInterface /** * @param Collection $entries * + * @return void + * */ public function setEntries(Collection $entries); diff --git a/app/Export/Collector/JournalExportCollector.php b/app/Export/Collector/JournalExportCollector.php index 57d65fc4e7..e9bfbca879 100644 --- a/app/Export/Collector/JournalExportCollector.php +++ b/app/Export/Collector/JournalExportCollector.php @@ -287,7 +287,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac } /** - * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ private function getWorkSet() { diff --git a/app/Export/Entry/Entry.php b/app/Export/Entry/Entry.php index 259dd675ab..53ace9643b 100644 --- a/app/Export/Entry/Entry.php +++ b/app/Export/Entry/Entry.php @@ -29,6 +29,7 @@ use Crypt; * * * Class Entry + * @SuppressWarnings(PHPMD.LongVariable) * * @package FireflyIII\Export\Entry */ @@ -71,33 +72,21 @@ final class Entry */ public static function fromObject($object): Entry { - $entry = new self; - - // journal information: - $entry->journal_id = $object->transaction_journal_id; - $entry->description = $object->journal_encrypted === 1 ? Crypt::decrypt($object->journal_description) : $object->journal_description; - $entry->amount = round($object->amount, 2); // always positive - $entry->date = $object->date; - $entry->transaction_type = $object->transaction_type; - $entry->currency_code = $object->transaction_currency_code; - - // source information: - $entry->source_account_id = $object->account_id; - $entry->source_account_name = $object->account_name_encrypted === 1 ? Crypt::decrypt($object->account_name) : $object->account_name; - - - // destination information + $entry = new self; + $entry->journal_id = $object->transaction_journal_id; + $entry->description = self::decrypt($object->journal_encrypted, $object->journal_description); + $entry->amount = $object->amount; + $entry->date = $object->date; + $entry->transaction_type = $object->transaction_type; + $entry->currency_code = $object->transaction_currency_code; + $entry->source_account_id = $object->account_id; + $entry->source_account_name = self::decrypt($object->account_name_encrypted, $object->account_name); $entry->destination_account_id = $object->opposing_account_id; - $entry->destination_account_name = $object->opposing_account_encrypted === 1 ? Crypt::decrypt($object->opposing_account_name) - : $object->opposing_account_name; - - - // category and budget - $entry->category_id = $object->category_id ?? ''; - $entry->category_name = $object->category_name ?? ''; - $entry->budget_id = $object->budget_id ?? ''; - $entry->budget_name = $object->budget_name ?? ''; - + $entry->destination_account_name = self::decrypt($object->opposing_account_encrypted, $object->opposing_account_name); + $entry->category_id = $object->category_id ?? ''; + $entry->category_name = $object->category_name ?? ''; + $entry->budget_id = $object->budget_id ?? ''; + $entry->budget_name = $object->budget_name ?? ''; // update description when transaction description is different: if (!is_null($object->description) && $object->description != $entry->description) { @@ -107,4 +96,19 @@ final class Entry return $entry; } + /** + * @param int $isEncrypted + * @param $value + * + * @return string + */ + protected static function decrypt(int $isEncrypted, $value) + { + if ($isEncrypted === 1) { + return Crypt::decrypt($value); + } + + return $value; + } + } diff --git a/app/Export/Exporter/ExporterInterface.php b/app/Export/Exporter/ExporterInterface.php index 9b8a4e4ed9..3559d89cec 100644 --- a/app/Export/Exporter/ExporterInterface.php +++ b/app/Export/Exporter/ExporterInterface.php @@ -40,6 +40,8 @@ interface ExporterInterface /** * @param Collection $entries * + * @return void + * */ public function setEntries(Collection $entries); diff --git a/app/Export/Processor.php b/app/Export/Processor.php index 15960d63f6..0d0421a460 100644 --- a/app/Export/Processor.php +++ b/app/Export/Processor.php @@ -155,7 +155,7 @@ class Processor implements ProcessorInterface $zip->close(); // delete the files: - $this->deleteFiles($disk); + $this->deleteFiles(); return true; } @@ -183,10 +183,11 @@ class Processor implements ProcessorInterface } /** - * @param FilesystemAdapter $disk + * */ - private function deleteFiles(FilesystemAdapter $disk) + private function deleteFiles() { + $disk = Storage::disk('export'); foreach ($this->getFiles() as $file) { $disk->delete($file); } diff --git a/app/Export/ProcessorInterface.php b/app/Export/ProcessorInterface.php index 459cbf6276..614d748304 100644 --- a/app/Export/ProcessorInterface.php +++ b/app/Export/ProcessorInterface.php @@ -27,6 +27,7 @@ interface ProcessorInterface * Processor constructor. * * @param array $settings + * */ public function __construct(array $settings); diff --git a/app/Generator/Chart/Basic/ChartJsGenerator.php b/app/Generator/Chart/Basic/ChartJsGenerator.php index 3f5eb8145c..85cff4d4f6 100644 --- a/app/Generator/Chart/Basic/ChartJsGenerator.php +++ b/app/Generator/Chart/Basic/ChartJsGenerator.php @@ -49,6 +49,7 @@ class ChartJsGenerator implements GeneratorInterface * ] * ] * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five. * * @param array $data * @@ -58,7 +59,7 @@ class ChartJsGenerator implements GeneratorInterface { reset($data); $first = current($data); - $labels = array_keys($first['entries']); + $labels = is_array($first['entries']) ? array_keys($first['entries']) : []; $chartData = [ 'count' => count($data), @@ -111,7 +112,7 @@ class ChartJsGenerator implements GeneratorInterface $value = bcmul($value, '-1'); } - $chartData['datasets'][0]['data'][] = round($value, 2); + $chartData['datasets'][0]['data'][] = $value; $chartData['datasets'][0]['backgroundColor'][] = ChartColour::getColour($index); $chartData['labels'][] = $key; $index++; diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php index a5f30e9a10..9c21b1a0ba 100644 --- a/app/Generator/Report/Audit/MonthReportGenerator.php +++ b/app/Generator/Report/Audit/MonthReportGenerator.php @@ -16,7 +16,7 @@ namespace FireflyIII\Generator\Report\Audit; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; use Illuminate\Support\Collection; @@ -49,33 +49,8 @@ class MonthReportGenerator implements ReportGeneratorInterface /** @var Account $account */ foreach ($this->accounts as $account) { // balance the day before: - $id = $account->id; - $dayBeforeBalance = Steam::balance($account, $dayBefore); - $collector = new JournalCollector(auth()->user()); - $collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end); - $journals = $collector->getJournals(); - $journals = $journals->reverse(); - $startBalance = $dayBeforeBalance; - - - /** @var Transaction $journal */ - foreach ($journals as $transaction) { - $transaction->before = $startBalance; - $transactionAmount = $transaction->transaction_amount; - $newBalance = bcadd($startBalance, $transactionAmount); - $transaction->after = $newBalance; - $startBalance = $newBalance; - } - - /* - * Reverse set again. - */ - $auditData[$id]['journals'] = $journals->reverse(); - $auditData[$id]['exists'] = $journals->count() > 0; - $auditData[$id]['end'] = $this->end->formatLocalized(strval(trans('config.month_and_day'))); - $auditData[$id]['endBalance'] = Steam::balance($account, $this->end); - $auditData[$id]['dayBefore'] = $dayBefore->formatLocalized(strval(trans('config.month_and_day'))); - $auditData[$id]['dayBeforeBalance'] = $dayBeforeBalance; + $id = $account->id; + $auditData[$id] = $this->getAuditReport($account, $dayBefore); } $defaultShow = ['icon', 'description', 'balance_before', 'amount', 'balance_after', 'date', 'to']; @@ -153,4 +128,46 @@ class MonthReportGenerator implements ReportGeneratorInterface return $this; } + + /** + * @param Account $account + * @param Carbon $date + * + * @return array + */ + private function getAuditReport(Account $account, Carbon $date): array + { + + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); + $collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end); + $journals = $collector->getJournals(); + $journals = $journals->reverse(); + $dayBeforeBalance = Steam::balance($account, $date); + $startBalance = $dayBeforeBalance; + + + /** @var Transaction $journal */ + foreach ($journals as $transaction) { + $transaction->before = $startBalance; + $transactionAmount = $transaction->transaction_amount; + $newBalance = bcadd($startBalance, $transactionAmount); + $transaction->after = $newBalance; + $startBalance = $newBalance; + } + + /* + * Reverse set again. + */ + $return = [ + 'journals' => $journals->reverse(), + 'exists' => $journals->count() > 0, + 'end' => $this->end->formatLocalized(strval(trans('config.month_and_day'))), + 'endBalance' => Steam::balance($account, $this->end), + 'dayBefore' => $date->formatLocalized(strval(trans('config.month_and_day'))), + 'dayBeforeBalance' => $dayBeforeBalance, + ]; + + return $return; + } } diff --git a/app/Generator/Report/Budget/MonthReportGenerator.php b/app/Generator/Report/Budget/MonthReportGenerator.php index 085fb9bfe8..38a53aad79 100644 --- a/app/Generator/Report/Budget/MonthReportGenerator.php +++ b/app/Generator/Report/Budget/MonthReportGenerator.php @@ -17,7 +17,7 @@ namespace FireflyIII\Generator\Report\Budget; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Generator\Report\Support; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; @@ -184,7 +184,8 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface return $this->expenses; } - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) ->setTypes([TransactionType::WITHDRAWAL]) ->setBudgets($this->budgets)->withOpposingAccount()->disableFilter(); diff --git a/app/Generator/Report/Category/MonthReportGenerator.php b/app/Generator/Report/Category/MonthReportGenerator.php index 53aa790e28..5517d7be13 100644 --- a/app/Generator/Report/Category/MonthReportGenerator.php +++ b/app/Generator/Report/Category/MonthReportGenerator.php @@ -17,7 +17,7 @@ namespace FireflyIII\Generator\Report\Category; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Generator\Report\Support; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; @@ -194,7 +194,8 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface return $this->expenses; } - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) ->setCategories($this->categories)->withOpposingAccount()->disableFilter(); @@ -216,7 +217,8 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface return $this->income; } - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) ->setCategories($this->categories)->withOpposingAccount(); @@ -229,6 +231,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface } /** + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. * @param array $spent * @param array $earned * diff --git a/app/Generator/Report/Support.php b/app/Generator/Report/Support.php index 1f71ad182a..573129ef97 100644 --- a/app/Generator/Report/Support.php +++ b/app/Generator/Report/Support.php @@ -34,27 +34,7 @@ class Support */ public static function filterExpenses(Collection $collection, array $accounts): Collection { - $result = $collection->filter( - function (Transaction $transaction) use ($accounts) { - $opposing = $transaction->opposing_account_id; - // remove internal transfer - if (in_array($opposing, $accounts)) { - Log::debug(sprintf('Filtered #%d because its opposite is in accounts.', $transaction->id)); - - return null; - } - // remove positive amount - if (bccomp($transaction->transaction_amount, '0') === 1) { - Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount)); - - return null; - } - - return $transaction; - } - ); - - return $result; + return self::filterTransactions($collection, $accounts, 1); } /** @@ -64,9 +44,21 @@ class Support * @return Collection */ public static function filterIncome(Collection $collection, array $accounts): Collection + { + return self::filterTransactions($collection, $accounts, -1); + } + + /** + * @param Collection $collection + * @param array $accounts + * @param int $modifier + * + * @return Collection + */ + public static function filterTransactions(Collection $collection, array $accounts, int $modifier): Collection { $result = $collection->filter( - function (Transaction $transaction) use ($accounts) { + function (Transaction $transaction) use ($accounts, $modifier) { $opposing = $transaction->opposing_account_id; // remove internal transfer if (in_array($opposing, $accounts)) { @@ -75,7 +67,7 @@ class Support return null; } // remove positive amount - if (bccomp($transaction->transaction_amount, '0') === -1) { + if (bccomp($transaction->transaction_amount, '0') === $modifier) { Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount)); return null; diff --git a/app/Handlers/Events/BudgetEventHandler.php b/app/Handlers/Events/BudgetEventHandler.php deleted file mode 100644 index 2acc5a29d9..0000000000 --- a/app/Handlers/Events/BudgetEventHandler.php +++ /dev/null @@ -1,93 +0,0 @@ -processRepetitionChange($budgetLimitEvent->budgetLimit, $budgetLimitEvent->end); - } - - /** - * Updates, if present the budget limit repetition part of a budget limit. - * - * @param UpdatedBudgetLimit $budgetLimitEvent - * - * @return bool - */ - public function updateRepetition(UpdatedBudgetLimit $budgetLimitEvent): bool - { - return $this->processRepetitionChange($budgetLimitEvent->budgetLimit, $budgetLimitEvent->end); - } - - /** - * @param BudgetLimit $budgetLimit - * @param Carbon $date - * - * @return bool - */ - private function processRepetitionChange(BudgetLimit $budgetLimit, Carbon $date): bool - { - $set = $budgetLimit->limitrepetitions() - ->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00')) - ->where('enddate', $date->format('Y-m-d 00:00:00')) - ->get(); - if ($set->count() == 0) { - $repetition = new LimitRepetition; - $repetition->startdate = $budgetLimit->startdate; - $repetition->enddate = $date; - $repetition->amount = $budgetLimit->amount; - $repetition->budgetLimit()->associate($budgetLimit); - - try { - $repetition->save(); - } catch (QueryException $e) { - Log::error('Trying to save new LimitRepetition failed: ' . $e->getMessage()); - } - } - - if ($set->count() == 1) { - $repetition = $set->first(); - $repetition->amount = $budgetLimit->amount; - $repetition->save(); - - } - - return true; - } -} diff --git a/app/Handlers/Events/StoredJournalEventHandler.php b/app/Handlers/Events/StoredJournalEventHandler.php index ade9536765..14e4e2a438 100644 --- a/app/Handlers/Events/StoredJournalEventHandler.php +++ b/app/Handlers/Events/StoredJournalEventHandler.php @@ -16,6 +16,7 @@ namespace FireflyIII\Handlers\Events; use FireflyIII\Events\StoredTransactionJournal; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankEvent; +use FireflyIII\Models\PiggyBankRepetition; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; use FireflyIII\Models\TransactionJournal; @@ -33,79 +34,40 @@ class StoredJournalEventHandler /** * This method connects a new transfer to a piggy bank. * - * @param StoredTransactionJournal $storedJournalEvent + * @param StoredTransactionJournal $event * * @return bool */ - public function connectToPiggyBank(StoredTransactionJournal $storedJournalEvent): bool + public function connectToPiggyBank(StoredTransactionJournal $event): bool { /** @var TransactionJournal $journal */ - $journal = $storedJournalEvent->journal; - $piggyBankId = $storedJournalEvent->piggyBankId; - + $journal = $event->journal; + $piggyBankId = $event->piggyBankId; Log::debug(sprintf('Trying to connect journal %d to piggy bank %d.', $journal->id, $piggyBankId)); - /** @var PiggyBank $piggyBank */ - $piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); - - if (is_null($piggyBank)) { - Log::error('No such piggy bank!'); - - return true; - } - Log::debug(sprintf('Found piggy bank #%d: "%s"', $piggyBank->id, $piggyBank->name)); - // update piggy bank rep for date of transaction journal. - $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); - if (is_null($repetition)) { - Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d'))); + /* + * Verify existence of piggy bank: + */ + if (!$this->verifyExistence($event)) { + Log::error(sprintf('No such piggy bank or no repetition on %s', $journal->date->format('Y-m-d'))); return true; } - $amount = TransactionJournal::amountPositive($journal); - Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); - // if piggy account matches source account, the amount is positive - $sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray(); - if (in_array($piggyBank->account_id, $sources)) { - $amount = bcmul($amount, '-1'); - Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id)); - } - - // if the amount is positive: - // make sure it fits in piggy bank: - if (bccomp($amount, '0') === 1) { - // amount is positive - $room = bcsub(strval($piggyBank->targetamount), strval($repetition->currentamount)); - Log::debug(sprintf('Room in piggy bank for extra money is %f', $room)); - if (bccomp($room, $amount) === -1) { - // $room is smaller than $amount - Log::debug(sprintf('There is NO room to add %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); - Log::debug(sprintf('New amount is %f', $room)); - $amount = $room; - } - } - - if (bccomp($amount, '0') === -1) { - // amount is negative - Log::debug(sprintf('Max amount to remove is %f', $repetition->currentamount)); - $compare = bcmul($repetition->currentamount, '-1'); - if (bccomp($compare, $amount) === 1) { - // $currentamount is smaller than $amount - Log::debug(sprintf('Cannot remove %f from piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); - Log::debug(sprintf('New amount is %f', $compare)); - $amount = $compare; - } - } - - + /* + * Get relevant data: + */ + $piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); + $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); + $amount = $this->getExactAmount($journal, $piggyBank, $repetition); $repetition->currentamount = bcadd($repetition->currentamount, $amount); $repetition->save(); - /** @var PiggyBankEvent $storedJournalEvent */ - $storedJournalEvent = PiggyBankEvent::create( + /** @var PiggyBankEvent $event */ + $event = PiggyBankEvent::create( ['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount] ); - Log::debug(sprintf('Created piggy bank event #%d', $storedJournalEvent->id)); + Log::debug(sprintf('Created piggy bank event #%d', $event->id)); return true; } @@ -161,4 +123,81 @@ class StoredJournalEventHandler return true; } + + /** + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but I can live with it. + * @param TransactionJournal $journal + * @param PiggyBank $piggyBank + * @param PiggyBankRepetition $repetition + * + * @return string + */ + private function getExactAmount(TransactionJournal $journal, PiggyBank $piggyBank, PiggyBankRepetition $repetition): string + { + $amount = TransactionJournal::amountPositive($journal); + $sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray(); + $room = bcsub(strval($piggyBank->targetamount), strval($repetition->currentamount)); + $compare = bcmul($repetition->currentamount, '-1'); + + Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); + + // if piggy account matches source account, the amount is positive + if (in_array($piggyBank->account_id, $sources)) { + $amount = bcmul($amount, '-1'); + Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id)); + } + + + // if the amount is positive, make sure it fits in piggy bank: + if (bccomp($amount, '0') === 1 && bccomp($room, $amount) === -1) { + // amount is positive and $room is smaller than $amount + Log::debug(sprintf('Room in piggy bank for extra money is %f', $room)); + Log::debug(sprintf('There is NO room to add %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); + Log::debug(sprintf('New amount is %f', $room)); + + return $room; + } + + // amount is negative and $currentamount is smaller than $amount + if (bccomp($amount, '0') === -1 && bccomp($compare, $amount) === 1) { + Log::debug(sprintf('Max amount to remove is %f', $repetition->currentamount)); + Log::debug(sprintf('Cannot remove %f from piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); + Log::debug(sprintf('New amount is %f', $compare)); + + return $compare; + } + + return $amount; + } + + /** + * @param StoredTransactionJournal $event + * + * @return bool + */ + private function verifyExistence(StoredTransactionJournal $event): bool + { + /** @var TransactionJournal $journal */ + $journal = $event->journal; + $piggyBankId = $event->piggyBankId; + + /** @var PiggyBank $piggyBank */ + $piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); + + if (is_null($piggyBank)) { + Log::error('No such piggy bank!'); + + return false; + } + Log::debug(sprintf('Found piggy bank #%d: "%s"', $piggyBank->id, $piggyBank->name)); + // update piggy bank rep for date of transaction journal. + $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); + if (is_null($repetition)) { + Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d'))); + + return false; + } + + return true; + } } diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index 0481dd516f..5144843bf7 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -13,25 +13,12 @@ declare(strict_types = 1); namespace FireflyIII\Handlers\Events; -use Exception; -use FireflyConfig; -use FireflyIII\Events\BlockedBadLogin; -use FireflyIII\Events\BlockedUseOfDomain; -use FireflyIII\Events\BlockedUseOfEmail; -use FireflyIII\Events\BlockedUserLogin; -use FireflyIII\Events\ConfirmedUser; -use FireflyIII\Events\DeletedUser; -use FireflyIII\Events\LockedOutUser; use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RequestedNewPassword; -use FireflyIII\Events\ResentConfirmation; -use FireflyIII\Models\Configuration; use FireflyIII\Repositories\User\UserRepositoryInterface; -use FireflyIII\User; use Illuminate\Mail\Message; use Log; use Mail; -use Preferences; use Session; use Swift_TransportException; @@ -42,7 +29,6 @@ use Swift_TransportException; * * The method name reflects what is being done. This is in the present tense. * - * * @package FireflyIII\Handlers\Events */ class UserEventHandler @@ -82,237 +68,6 @@ class UserEventHandler return true; } - /** - * @param BlockedBadLogin $event - * - * @return bool - */ - public function reportBadLogin(BlockedBadLogin $event) - { - $email = $event->email; - $owner = env('SITE_OWNER'); - $ipAddress = $event->ipAddress; - /** @var Configuration $sendmail */ - $sendmail = FireflyConfig::get('mail_for_bad_login', config('firefly.configuration.mail_for_bad_login')); - Log::debug(sprintf('Now in reportBadLogin for email address %s', $email)); - Log::error(sprintf('User %s tried to login with bad credentials.', $email)); - if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) { - - return true; - } - - try { - Mail::send( - ['emails.blocked-bad-creds-html', 'emails.blocked-bad-creds-text'], ['email' => $email, 'ip' => $ipAddress], - function (Message $message) use ($owner) { - $message->to($owner, $owner)->subject('Blocked login attempt with bad credentials'); - } - ); - } catch (Swift_TransportException $e) { - Log::error($e->getMessage()); - } - - return true; - } - - /** - * @param BlockedUserLogin $event - * - * @return bool - */ - public function reportBlockedUser(BlockedUserLogin $event): bool - { - $user = $event->user; - $owner = env('SITE_OWNER'); - $email = $user->email; - $ipAddress = $event->ipAddress; - /** @var Configuration $sendmail */ - $sendmail = FireflyConfig::get('mail_for_blocked_login', config('firefly.configuration.mail_for_blocked_login')); - Log::debug(sprintf('Now in reportBlockedUser for email address %s', $email)); - Log::error(sprintf('User #%d (%s) has their accout blocked (blocked_code is "%s") but tried to login.', $user->id, $email, $user->blocked_code)); - if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) { - return true; - } - - // send email message: - try { - Mail::send( - ['emails.blocked-login-html', 'emails.blocked-login-text'], - [ - 'user_id' => $user->id, - 'user_address' => $email, - 'ip' => $ipAddress, - 'code' => $user->blocked_code, - ], function (Message $message) use ($owner, $user) { - $message->to($owner, $owner)->subject('Blocked login attempt of blocked user'); - } - ); - } catch (Swift_TransportException $e) { - Log::error($e->getMessage()); - } - - return true; - } - - /** - * @param LockedOutUser $event - * - * @return bool - */ - public function reportLockout(LockedOutUser $event): bool - { - $email = $event->email; - $owner = env('SITE_OWNER'); - $ipAddress = $event->ipAddress; - /** @var Configuration $sendmail */ - $sendmail = FireflyConfig::get('mail_for_lockout', config('firefly.configuration.mail_for_lockout')); - Log::debug(sprintf('Now in respondToLockout for email address %s', $email)); - Log::error(sprintf('User %s was locked out after too many invalid login attempts.', $email)); - if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) { - return true; - } - - // send email message: - try { - Mail::send( - ['emails.locked-out-html', 'emails.locked-out-text'], ['email' => $email, 'ip' => $ipAddress], function (Message $message) use ($owner) { - $message->to($owner, $owner)->subject('User was locked out'); - } - ); - } catch (Swift_TransportException $e) { - Log::error($e->getMessage()); - } - - return true; - } - - /** - * @param BlockedUseOfDomain $event - * - * @return bool - */ - public function reportUseBlockedDomain(BlockedUseOfDomain $event): bool - { - $email = $event->email; - $owner = env('SITE_OWNER'); - $ipAddress = $event->ipAddress; - $parts = explode('@', $email); - /** @var Configuration $sendmail */ - $sendmail = FireflyConfig::get('mail_for_blocked_domain', config('firefly.configuration.mail_for_blocked_domain')); - Log::debug(sprintf('Now in reportUseBlockedDomain for email address %s', $email)); - Log::error(sprintf('Somebody tried to register using an email address (%s) connected to a banned domain (%s).', $email, $parts[1])); - if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) { - return true; - } - - // send email message: - try { - Mail::send( - ['emails.blocked-registration-html', 'emails.blocked-registration-text'], - [ - 'email_address' => $email, - 'blocked_domain' => $parts[1], - 'ip' => $ipAddress, - ], function (Message $message) use ($owner) { - $message->to($owner, $owner)->subject('Blocked registration attempt with blocked domain'); - } - ); - } catch (Swift_TransportException $e) { - Log::error($e->getMessage()); - } - - return true; - } - - /** - * @param BlockedUseOfEmail $event - * - * @return bool - */ - public function reportUseOfBlockedEmail(BlockedUseOfEmail $event): bool - { - $email = $event->email; - $owner = env('SITE_OWNER'); - $ipAddress = $event->ipAddress; - /** @var Configuration $sendmail */ - $sendmail = FireflyConfig::get('mail_for_blocked_email', config('firefly.configuration.mail_for_blocked_email')); - Log::debug(sprintf('Now in reportUseOfBlockedEmail for email address %s', $email)); - Log::error(sprintf('Somebody tried to register using email address %s which is blocked (SHA2 hash).', $email)); - if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) { - return true; - } - - // send email message: - try { - Mail::send( - ['emails.blocked-email-html', 'emails.blocked-email-text'], - [ - 'user_address' => $email, - 'ip' => $ipAddress, - ], function (Message $message) use ($owner) { - $message->to($owner, $owner)->subject('Blocked registration attempt with blocked email address'); - } - ); - } catch (Swift_TransportException $e) { - Log::error($e->getMessage()); - } - - return true; - } - - /** - * @param DeletedUser $event - * - * @return bool - */ - public function saveEmailAddress(DeletedUser $event): bool - { - Preferences::mark(); - $email = hash('sha256', $event->email); - Log::debug(sprintf('Hash of email is %s', $email)); - /** @var Configuration $configuration */ - $configuration = FireflyConfig::get('deleted_users', []); - $content = $configuration->data; - if (!is_array($content)) { - $content = []; - } - $content[] = $email; - $configuration->data = $content; - Log::debug('New content of deleted_users is ', $content); - FireflyConfig::set('deleted_users', $content); - - Preferences::mark(); - - return true; - } - - /** - * This method will send a newly registered user a confirmation message, urging him or her to activate their account. - * - * @param RegisteredUser $event - * - * @return bool - */ - public function sendConfirmationMessage(RegisteredUser $event): bool - { - return $this->sendConfirmation($event->user, $event->ipAddress); - } - - /** - * If the user has somehow lost his or her confirmation message, this event will send it to the user again. - * - * At the moment, this method is exactly the same as the ::sendConfirmationMessage method, but that will change. - * - * @param ResentConfirmation $event - * - * @return bool - */ - function sendConfirmationMessageAgain(ResentConfirmation $event): bool - { - return $this->sendConfirmation($event->user, $event->ipAddress); - - } - /** * @param RequestedNewPassword $event * @@ -372,75 +127,4 @@ class UserEventHandler return true; } - - /** - * When the user is confirmed, this method stores the IP address of the user - * as a preference. Since this preference cannot be edited, it is effectively hidden - * from the user yet stored conveniently. - * - * @param ConfirmedUser $event - * - * @return bool - */ - public function storeConfirmationIpAddress(ConfirmedUser $event): bool - { - Preferences::setForUser($event->user, 'confirmation_ip_address', $event->ipAddress); - - return true; - } - - /** - * This message stores the users IP address on registration, in much the same - * fashion as the previous method. - * - * @param RegisteredUser $event - * - * @return bool - */ - public function storeRegistrationIpAddress(RegisteredUser $event): bool - { - Preferences::setForUser($event->user, 'registration_ip_address', $event->ipAddress); - - return true; - - } - - /** - * @param User $user - * @param string $ipAddress - * - * @return bool - */ - private function sendConfirmation(User $user, string $ipAddress): bool - { - $mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data; - if ($mustConfirmAccount === false) { - Preferences::setForUser($user, 'user_confirmed', true); - Preferences::setForUser($user, 'user_confirmed_last_mail', 0); - Preferences::mark(); - - return true; - } - $email = $user->email; - $code = str_random(16); - $route = route('do_confirm_account', [$code]); - Preferences::setForUser($user, 'user_confirmed', false); - Preferences::setForUser($user, 'user_confirmed_last_mail', time()); - Preferences::setForUser($user, 'user_confirmed_code', $code); - try { - Mail::send( - ['emails.confirm-account-html', 'emails.confirm-account-text'], ['route' => $route, 'ip' => $ipAddress], - function (Message $message) use ($email) { - $message->to($email, $email)->subject('Please confirm your Firefly III account'); - } - ); - } catch (Swift_TransportException $e) { - Log::error($e->getMessage()); - } catch (Exception $e) { - Log::error($e->getMessage()); - } - - return true; - } - } diff --git a/app/Helpers/Attachments/AttachmentHelper.php b/app/Helpers/Attachments/AttachmentHelper.php index 2c7e2d2b60..501842a7ce 100644 --- a/app/Helpers/Attachments/AttachmentHelper.php +++ b/app/Helpers/Attachments/AttachmentHelper.php @@ -16,7 +16,6 @@ use Crypt; use FireflyIII\Models\Attachment; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\MessageBag; -use Input; use Storage; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -81,20 +80,19 @@ class AttachmentHelper implements AttachmentHelperInterface } /** - * @param Model $model + * @param Model $model + * @param array|null $files * * @return bool */ - public function saveAttachmentsForModel(Model $model): bool + public function saveAttachmentsForModel(Model $model, array $files = null): bool { - $files = $this->getFiles(); - - if (!is_null($files) && !is_array($files)) { - $this->processFile($files, $model); - } - if (is_array($files)) { - $this->processFiles($files, $model); + foreach ($files as $entry) { + if (!is_null($entry)) { + $this->processFile($entry, $model); + } + } } return true; @@ -227,37 +225,4 @@ class AttachmentHelper implements AttachmentHelperInterface return true; } - - /** - * @return array|null|UploadedFile - */ - private function getFiles() - { - $files = null; - if (Input::hasFile('attachments')) { - $files = Input::file('attachments'); - } - - return $files; - } - - /** - * @param array $files - * - * @param Model $model - * - * @return bool - */ - private function processFiles(array $files, Model $model): bool - { - foreach ($files as $entry) { - if (!is_null($entry)) { - $this->processFile($entry, $model); - } - } - - return true; - } - - } diff --git a/app/Helpers/Attachments/AttachmentHelperInterface.php b/app/Helpers/Attachments/AttachmentHelperInterface.php index ba98fd07db..dc6f9420aa 100644 --- a/app/Helpers/Attachments/AttachmentHelperInterface.php +++ b/app/Helpers/Attachments/AttachmentHelperInterface.php @@ -46,6 +46,6 @@ interface AttachmentHelperInterface * * @return bool */ - public function saveAttachmentsForModel(Model $model): bool; + public function saveAttachmentsForModel(Model $model, array $files = null): bool; } diff --git a/app/Helpers/Chart/MetaPieChart.php b/app/Helpers/Chart/MetaPieChart.php new file mode 100644 index 0000000000..fbeb70cc25 --- /dev/null +++ b/app/Helpers/Chart/MetaPieChart.php @@ -0,0 +1,278 @@ + ['opposing_account_id'], + 'budget' => ['transaction_journal_budget_id', 'transaction_budget_id'], + 'category' => ['transaction_journal_category_id', 'transaction_category_id'], + ]; + + /** @var array */ + protected $repositories + = [ + 'account' => AccountRepositoryInterface::class, + 'budget' => BudgetRepositoryInterface::class, + 'category' => CategoryRepositoryInterface::class, + ]; + + + /** @var Carbon */ + protected $start; + /** @var string */ + protected $total = '0'; + /** @var User */ + protected $user; + + public function __construct() + { + $this->accounts = new Collection; + $this->budgets = new Collection; + $this->categories = new Collection; + } + + /** + * @param string $direction + * @param string $group + * + * @return array + */ + public function generate(string $direction, string $group): array + { + $transactions = $this->getTransactions($direction); + $grouped = $this->groupByFields($transactions, $this->grouping[$group]); + $chartData = $this->organizeByType($group, $grouped); + + // also collect all other transactions + if ($this->collectOtherObjects && $direction === 'expense') { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [$this->user]); + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::WITHDRAWAL]); + $journals = $collector->getJournals(); + $sum = strval($journals->sum('transaction_amount')); + $sum = bcmul($sum, '-1'); + $sum = bcsub($sum, $this->total); + $chartData[strval(trans('firefly.everything_else'))] = $sum; + } + + if ($this->collectOtherObjects && $direction === 'income') { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::DEPOSIT]); + $journals = $collector->getJournals(); + $sum = strval($journals->sum('transaction_amount')); + $sum = bcsub($sum, $this->total); + $chartData[strval(trans('firefly.everything_else'))] = $sum; + } + + return $chartData; + + } + + /** + * @param Collection $accounts + * + * @return MetaPieChartInterface + */ + public function setAccounts(Collection $accounts): MetaPieChartInterface + { + $this->accounts = $accounts; + + return $this; + } + + /** + * @param Collection $budgets + * + * @return MetaPieChartInterface + */ + public function setBudgets(Collection $budgets): MetaPieChartInterface + { + $this->budgets = $budgets; + + return $this; + } + + /** + * @param Collection $categories + * + * @return MetaPieChartInterface + */ + public function setCategories(Collection $categories): MetaPieChartInterface + { + $this->categories = $categories; + + return $this; + } + + /** + * @param bool $collectOtherObjects + * + * @return MetaPieChartInterface + */ + public function setCollectOtherObjects(bool $collectOtherObjects): MetaPieChartInterface + { + $this->collectOtherObjects = $collectOtherObjects; + + return $this; + } + + /** + * @param Carbon $end + * + * @return MetaPieChartInterface + */ + public function setEnd(Carbon $end): MetaPieChartInterface + { + $this->end = $end; + + return $this; + } + + /** + * @param Carbon $start + * + * @return MetaPieChartInterface + */ + public function setStart(Carbon $start): MetaPieChartInterface + { + $this->start = $start; + + return $this; + } + + /** + * @param User $user + * + * @return MetaPieChartInterface + */ + public function setUser(User $user): MetaPieChartInterface + { + $this->user = $user; + + return $this; + } + + protected function getTransactions(string $direction) + { + $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; + $modifier = -1; + if ($direction === 'expense') { + $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; + $modifier = 1; + } + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); + $collector->setAccounts($this->accounts); + $collector->setRange($this->start, $this->end); + $collector->setTypes($types); + $collector->withOpposingAccount(); + + if ($direction === 'income') { + $collector->disableFilter(); + } + + if ($this->budgets->count() > 0) { + $collector->setBudgets($this->budgets); + } + if ($this->categories->count() > 0) { + $collector->setCategories($this->categories); + } + + $accountIds = $this->accounts->pluck('id')->toArray(); + $transactions = $collector->getJournals(); + $set = Support::filterTransactions($transactions, $accountIds, $modifier); + + return $set; + } + + /** + * @param Collection $set + * @param array $fields + * + * @return array + */ + protected function groupByFields(Collection $set, array $fields) + { + $grouped = []; + /** @var Transaction $transaction */ + foreach ($set as $transaction) { + $values = []; + foreach ($fields as $field) { + $values[] = intval($transaction->$field); + } + $value = max($values); + $grouped[$value] = $grouped[$value] ?? '0'; + $grouped[$value] = bcadd($transaction->transaction_amount, $grouped[$value]); + } + + return $grouped; + } + + /** + * @param string $type + * @param array $array + * + * @return array + */ + protected function organizeByType(string $type, array $array): array + { + $chartData = []; + $names = []; + $repository = app($this->repositories[$type], [$this->user]); + foreach ($array as $objectId => $amount) { + if (!isset($names[$objectId])) { + $object = $repository->find(intval($objectId)); + $names[$objectId] = $object->name; + } + if (bccomp($amount, '0') === -1) { + $amount = bcmul($amount, '-1'); + } + + $this->total = bcadd($this->total, $amount); + $chartData[$names[$objectId]] = $amount; + } + + return $chartData; + + } +} \ No newline at end of file diff --git a/app/Helpers/Chart/MetaPieChartInterface.php b/app/Helpers/Chart/MetaPieChartInterface.php new file mode 100644 index 0000000000..a466db8a38 --- /dev/null +++ b/app/Helpers/Chart/MetaPieChartInterface.php @@ -0,0 +1,82 @@ +budget = $budget; } + /** + * @return BudgetLimit + */ + public function getBudgetLimit(): BudgetLimit + { + return $this->budgetLimit; + } + + /** + * @param BudgetLimit $budgetLimit + */ + public function setBudgetLimit(BudgetLimit $budgetLimit) + { + $this->budgetLimit = $budgetLimit; + } + /** * @return Carbon */ public function getEndDate() { - return $this->endDate; - } - - /** - * @param Carbon $endDate - */ - public function setEndDate($endDate) - { - $this->endDate = $endDate; + return $this->budgetLimit->end_date ?? new Carbon; } /** @@ -127,18 +134,11 @@ class BalanceLine */ public function getStartDate() { - return $this->startDate; - } - - /** - * @param Carbon $startDate - */ - public function setStartDate($startDate) - { - $this->startDate = $startDate; + return $this->budgetLimit->start_date ?? new Carbon; } /** + * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @return string */ public function getTitle(): string @@ -147,13 +147,13 @@ class BalanceLine return $this->getBudget()->name; } if ($this->getRole() == self::ROLE_DEFAULTROLE) { - return trans('firefly.no_budget'); + return strval(trans('firefly.no_budget')); } if ($this->getRole() == self::ROLE_TAGROLE) { - return trans('firefly.coveredWithTags'); + return strval(trans('firefly.coveredWithTags')); } if ($this->getRole() == self::ROLE_DIFFROLE) { - return trans('firefly.leftUnbalanced'); + return strval(trans('firefly.leftUnbalanced')); } return ''; @@ -169,7 +169,7 @@ class BalanceLine */ public function leftOfRepetition(): string { - $start = $this->budget->amount ?? '0'; + $start = $this->budgetLimit->amount ?? '0'; /** @var BalanceEntry $balanceEntry */ foreach ($this->getBalanceEntries() as $balanceEntry) { $start = bcadd($balanceEntry->getSpent(), $start); diff --git a/app/Helpers/Collection/Budget.php b/app/Helpers/Collection/Budget.php index 8821940b3e..f590cb6216 100644 --- a/app/Helpers/Collection/Budget.php +++ b/app/Helpers/Collection/Budget.php @@ -60,7 +60,6 @@ class Budget */ public function addBudgeted(string $add): Budget { - $add = strval(round($add, 2)); $this->budgeted = bcadd($this->budgeted, $add); return $this; @@ -73,7 +72,6 @@ class Budget */ public function addLeft(string $add): Budget { - $add = strval(round($add, 2)); $this->left = bcadd($this->left, $add); return $this; @@ -86,7 +84,6 @@ class Budget */ public function addOverspent(string $add): Budget { - $add = strval(round($add, 2)); $this->overspent = bcadd($this->overspent, $add); return $this; @@ -99,7 +96,6 @@ class Budget */ public function addSpent(string $add): Budget { - $add = strval(round($add, 2)); $this->spent = bcadd($this->spent, $add); return $this; @@ -168,7 +164,7 @@ class Budget */ public function setOverspent(string $overspent): Budget { - $this->overspent = strval(round($overspent, 2)); + $this->overspent = $overspent; return $this; } @@ -188,7 +184,7 @@ class Budget */ public function setSpent(string $spent): Budget { - $this->spent = strval(round($spent, 2)); + $this->spent = $spent; return $this; } diff --git a/app/Helpers/Collection/BudgetLine.php b/app/Helpers/Collection/BudgetLine.php index 5538c9e8f1..03bf59f8ff 100644 --- a/app/Helpers/Collection/BudgetLine.php +++ b/app/Helpers/Collection/BudgetLine.php @@ -13,7 +13,7 @@ declare(strict_types = 1); namespace FireflyIII\Helpers\Collection; use FireflyIII\Models\Budget as BudgetModel; -use FireflyIII\Models\LimitRepetition; +use FireflyIII\Models\BudgetLimit; /** * @@ -26,14 +26,14 @@ class BudgetLine /** @var BudgetModel */ protected $budget; + /** @var BudgetLimit */ + protected $budgetLimit; /** @var string */ protected $budgeted = '0'; /** @var string */ protected $left = '0'; /** @var string */ protected $overspent = '0'; - /** @var LimitRepetition */ - protected $repetition; /** @var string */ protected $spent = '0'; @@ -57,6 +57,26 @@ class BudgetLine return $this; } + /** + * @return BudgetLimit + */ + public function getBudgetLimit(): BudgetLimit + { + return $this->budgetLimit ?? new BudgetLimit; + } + + /** + * @param BudgetLimit $budgetLimit + * + * @return BudgetLimit + */ + public function setBudgetLimit(BudgetLimit $budgetLimit): BudgetLine + { + $this->budgetLimit = $budgetLimit; + + return $this; + } + /** * @return string */ @@ -117,26 +137,6 @@ class BudgetLine return $this; } - /** - * @return LimitRepetition - */ - public function getRepetition(): LimitRepetition - { - return $this->repetition ?? new LimitRepetition; - } - - /** - * @param LimitRepetition $repetition - * - * @return BudgetLine - */ - public function setRepetition(LimitRepetition $repetition): BudgetLine - { - $this->repetition = $repetition; - - return $this; - } - /** * @return string */ diff --git a/app/Helpers/Collection/Category.php b/app/Helpers/Collection/Category.php index 7090d387ba..faa4119a86 100644 --- a/app/Helpers/Collection/Category.php +++ b/app/Helpers/Collection/Category.php @@ -55,7 +55,6 @@ class Category */ public function addTotal(string $add) { - $add = strval(round($add, 2)); $this->total = bcadd($this->total, $add); } @@ -79,7 +78,7 @@ class Category */ public function getTotal(): string { - return strval(round($this->total, 2)); + return $this->total; } diff --git a/app/Helpers/Collector/JournalCollector.php b/app/Helpers/Collector/JournalCollector.php index c874c9949e..d5d881196a 100644 --- a/app/Helpers/Collector/JournalCollector.php +++ b/app/Helpers/Collector/JournalCollector.php @@ -563,7 +563,7 @@ class JournalCollector implements JournalCollectorInterface return $set; } if ($this->joinedOpposing === false) { - Log::error('Cannot filter internal transfers because no opposing information is present.'); + Log::info('Cannot filter internal transfers because no opposing information is present.'); return $set; } @@ -649,9 +649,17 @@ class JournalCollector implements JournalCollectorInterface // join some extra tables: $this->joinedBudget = true; $this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + $this->query->leftJoin('budgets as transaction_journal_budgets', 'transaction_journal_budgets.id', '=', 'budget_transaction_journal.budget_id'); $this->query->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id'); + $this->query->leftJoin('budgets as transaction_budgets', 'transaction_budgets.id', '=', 'budget_transaction.budget_id'); + $this->fields[] = 'budget_transaction_journal.budget_id as transaction_journal_budget_id'; + $this->fields[] = 'transaction_journal_budgets.encrypted as transaction_journal_budget_encrypted'; + $this->fields[] = 'transaction_journal_budgets.name as transaction_journal_budget_name'; + $this->fields[] = 'budget_transaction.budget_id as transaction_budget_id'; + $this->fields[] = 'transaction_budgets.encrypted as transaction_budget_encrypted'; + $this->fields[] = 'transaction_budgets.name as transaction_budget_name'; } } @@ -664,12 +672,26 @@ class JournalCollector implements JournalCollectorInterface // join some extra tables: $this->joinedCategory = true; $this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + $this->query->leftJoin( + 'categories as transaction_journal_categories', 'transaction_journal_categories.id', '=', 'category_transaction_journal.category_id' + ); + $this->query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id'); + $this->query->leftJoin('categories as transaction_categories', 'transaction_categories.id', '=', 'category_transaction.category_id'); + $this->fields[] = 'category_transaction_journal.category_id as transaction_journal_category_id'; + $this->fields[] = 'transaction_journal_categories.encrypted as transaction_journal_category_encrypted'; + $this->fields[] = 'transaction_journal_categories.name as transaction_journal_category_name'; + $this->fields[] = 'category_transaction.category_id as transaction_category_id'; + $this->fields[] = 'transaction_categories.encrypted as transaction_category_encrypted'; + $this->fields[] = 'transaction_categories.name as transaction_category_name'; } } + /** + * + */ private function joinOpposingTables() { if (!$this->joinedOpposing) { diff --git a/app/Helpers/Help/Help.php b/app/Helpers/Help/Help.php index 0d290955ca..12f01670dd 100644 --- a/app/Helpers/Help/Help.php +++ b/app/Helpers/Help/Help.php @@ -74,12 +74,8 @@ class Help implements HelpInterface $converter = new CommonMarkConverter(); $content = $converter->convertToHtml($content); } - if (strlen($content) === 0) { - Log::warning('Raw content length is zero.'); - } return $content; - } /** diff --git a/app/Helpers/Report/BalanceReportHelper.php b/app/Helpers/Report/BalanceReportHelper.php index 18cdd71acc..b1d53731e7 100644 --- a/app/Helpers/Report/BalanceReportHelper.php +++ b/app/Helpers/Report/BalanceReportHelper.php @@ -19,8 +19,7 @@ use FireflyIII\Helpers\Collection\Balance; use FireflyIII\Helpers\Collection\BalanceEntry; use FireflyIII\Helpers\Collection\BalanceHeader; use FireflyIII\Helpers\Collection\BalanceLine; -use FireflyIII\Models\Budget; -use FireflyIII\Models\LimitRepetition; +use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\Tag; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; @@ -31,6 +30,7 @@ use Log; /** * Class BalanceReportHelper * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) // I can't really help it. * @package FireflyIII\Helpers\Report */ class BalanceReportHelper implements BalanceReportHelperInterface @@ -61,19 +61,17 @@ class BalanceReportHelper implements BalanceReportHelperInterface public function getBalanceReport(Collection $accounts, Carbon $start, Carbon $end): Balance { Log::debug('Start of balance report'); - $balance = new Balance; - $header = new BalanceHeader; - $limitRepetitions = $this->budgetRepository->getAllBudgetLimitRepetitions($start, $end); + $balance = new Balance; + $header = new BalanceHeader; + $budgetLimits = $this->budgetRepository->getAllBudgetLimits($start, $end); foreach ($accounts as $account) { Log::debug(sprintf('Add account %s to headers.', $account->name)); $header->addAccount($account); } - /** @var LimitRepetition $repetition */ - foreach ($limitRepetitions as $repetition) { - $budget = $this->budgetRepository->find($repetition->budget_id); - Log::debug(sprintf('Create balance line for budget #%d ("%s") and repetition #%d', $budget->id, $budget->name, $repetition->id)); - $line = $this->createBalanceLine($budget, $repetition, $accounts); + /** @var BudgetLimit $budgetLimit */ + foreach ($budgetLimits as $budgetLimit) { + $line = $this->createBalanceLine($budgetLimit, $accounts); $balance->addBalanceLine($line); } Log::debug('Create rest of the things.'); @@ -145,26 +143,23 @@ class BalanceReportHelper implements BalanceReportHelperInterface /** - * @param Budget $budget - * @param LimitRepetition $repetition - * @param Collection $accounts + * @param BudgetLimit $budgetLimit + * @param Collection $accounts * * @return BalanceLine */ - private function createBalanceLine(Budget $budget, LimitRepetition $repetition, Collection $accounts): BalanceLine + private function createBalanceLine(BudgetLimit $budgetLimit, Collection $accounts): BalanceLine { - $line = new BalanceLine; - $budget->amount = $repetition->amount; - $line->setBudget($budget); - $line->setStartDate($repetition->startdate); - $line->setEndDate($repetition->enddate); + $line = new BalanceLine; + $line->setBudget($budgetLimit->budget); + $line->setBudgetLimit($budgetLimit); // loop accounts: foreach ($accounts as $account) { $balanceEntry = new BalanceEntry; $balanceEntry->setAccount($account); $spent = $this->budgetRepository->spentInPeriod( - new Collection([$budget]), new Collection([$account]), $repetition->startdate, $repetition->enddate + new Collection([$budgetLimit->budget]), new Collection([$account]), $budgetLimit->start_date, $budgetLimit->end_date ); $balanceEntry->setSpent($spent); $line->addBalanceEntry($balanceEntry); diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php index d79b9617b3..cf6836ce96 100644 --- a/app/Helpers/Report/BudgetReportHelper.php +++ b/app/Helpers/Report/BudgetReportHelper.php @@ -18,7 +18,7 @@ use Carbon\Carbon; use FireflyIII\Helpers\Collection\Budget as BudgetCollection; use FireflyIII\Helpers\Collection\BudgetLine; use FireflyIII\Models\Budget; -use FireflyIII\Models\LimitRepetition; +use FireflyIII\Models\BudgetLimit; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use Illuminate\Support\Collection; @@ -43,6 +43,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface } /** + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5. * @param Carbon $start * @param Carbon $end * @param Collection $accounts @@ -56,13 +57,9 @@ class BudgetReportHelper implements BudgetReportHelperInterface /** @var Budget $budget */ foreach ($set as $budget) { - $repetitions = $budget->limitrepetitions()->before($end)->after($start)->get(); - - // no repetition(s) for this budget: - if ($repetitions->count() == 0) { - // spent for budget in time range: - $spent = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end); - + $budgetLimits = $this->repository->getBudgetLimits($budget, $start, $end); + if ($budgetLimits->count() == 0) { // no budget limit(s) for this budget + $spent = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);// spent for budget in time range if ($spent > 0) { $budgetLine = new BudgetLine; $budgetLine->setBudget($budget)->setOverspent($spent); @@ -70,26 +67,20 @@ class BudgetReportHelper implements BudgetReportHelperInterface } continue; } - // one or more repetitions for budget: - /** @var LimitRepetition $repetition */ - foreach ($repetitions as $repetition) { - $data = $this->calculateExpenses($budget, $repetition, $accounts); - + /** @var BudgetLimit $budgetLimit */ + foreach ($budgetLimits as $budgetLimit) { // one or more repetitions for budget + $data = $this->calculateExpenses($budget, $budgetLimit, $accounts); $budgetLine = new BudgetLine; - $budgetLine->setBudget($budget)->setRepetition($repetition) + $budgetLine->setBudget($budget)->setBudgetLimit($budgetLimit) ->setLeft($data['left'])->setSpent($data['expenses'])->setOverspent($data['overspent']) - ->setBudgeted(strval($repetition->amount)); + ->setBudgeted(strval($budgetLimit->amount)); - $object->addBudgeted(strval($repetition->amount))->addSpent($data['spent']) + $object->addBudgeted(strval($budgetLimit->amount))->addSpent($data['spent']) ->addLeft($data['left'])->addOverspent($data['overspent'])->addBudgetLine($budgetLine); } - } - - // stuff outside of budgets: - - $noBudget = $this->repository->spentInPeriodWithoutBudget($accounts, $start, $end); + $noBudget = $this->repository->spentInPeriodWithoutBudget($accounts, $start, $end); // stuff outside of budgets $budgetLine = new BudgetLine; $budgetLine->setOverspent($noBudget)->setSpent($noBudget); $object->addOverspent($noBudget)->addBudgetLine($budgetLine); @@ -128,19 +119,19 @@ class BudgetReportHelper implements BudgetReportHelperInterface } /** - * @param Budget $budget - * @param LimitRepetition $repetition - * @param Collection $accounts + * @param Budget $budget + * @param BudgetLimit $budgetLimit + * @param Collection $accounts * * @return array */ - private function calculateExpenses(Budget $budget, LimitRepetition $repetition, Collection $accounts): array + private function calculateExpenses(Budget $budget, BudgetLimit $budgetLimit, Collection $accounts): array { $array = []; - $expenses = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $repetition->startdate, $repetition->enddate); - $array['left'] = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? bcadd($repetition->amount, $expenses) : '0'; - $array['spent'] = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? $expenses : '0'; - $array['overspent'] = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? '0' : bcadd($expenses, $repetition->amount); + $expenses = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $budgetLimit->start_date, $budgetLimit->end_date); + $array['left'] = bccomp(bcadd($budgetLimit->amount, $expenses), '0') === 1 ? bcadd($budgetLimit->amount, $expenses) : '0'; + $array['spent'] = bccomp(bcadd($budgetLimit->amount, $expenses), '0') === 1 ? $expenses : '0'; + $array['overspent'] = bccomp(bcadd($budgetLimit->amount, $expenses), '0') === 1 ? '0' : bcadd($expenses, $budgetLimit->amount); $array['expenses'] = $expenses; return $array; diff --git a/app/Helpers/Report/ReportHelper.php b/app/Helpers/Report/ReportHelper.php index ae0e4fa800..71d80119a4 100644 --- a/app/Helpers/Report/ReportHelper.php +++ b/app/Helpers/Report/ReportHelper.php @@ -53,6 +53,8 @@ class ReportHelper implements ReportHelperInterface * This method generates a full report for the given period on all * the users bills and their payments. * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5. + * * Excludes bills which have not had a payment on the mentioned accounts. * * @param Carbon $start @@ -80,8 +82,6 @@ class ReportHelper implements ReportHelperInterface $billLine->setMin(strval($bill->amount_min)); $billLine->setMax(strval($bill->amount_max)); $billLine->setHit(false); - // is hit in period? - $entry = $journals->filter( function (Transaction $transaction) use ($bill) { return $transaction->bill_id === $bill->id; @@ -94,14 +94,10 @@ class ReportHelper implements ReportHelperInterface $billLine->setLastHitDate($first->date); $billLine->setHit(true); } - - // bill is active, or bill is hit: if ($billLine->isActive() || $billLine->isHit()) { $collection->addBill($billLine); } } - - // do some extra filtering. $collection->filterBills(); return $collection; diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index eea03cbac5..8549e18907 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -17,7 +17,6 @@ use Amount; use Carbon\Carbon; use ExpandedForm; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\JournalCollector; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Requests\AccountFormRequest; use FireflyIII\Models\Account; @@ -28,8 +27,8 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Account\AccountTaskerInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\CacheProperties; +use Illuminate\Http\Request; use Illuminate\Support\Collection; -use Input; use Log; use Navigation; use Preferences; @@ -117,18 +116,19 @@ class AccountController extends Controller } /** + * @param Request $request * @param ARI $repository * @param Account $account * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function destroy(ARI $repository, Account $account) + public function destroy(Request $request, ARI $repository, Account $account) { $type = $account->accountType->type; $typeName = config('firefly.shortNamesByFullName.' . $type); $name = $account->name; $accountId = $account->id; - $moveTo = $repository->find(intval(Input::get('move_account_before_delete'))); + $moveTo = $repository->find(intval($request->get('move_account_before_delete'))); $repository->destroy($account, $moveTo); @@ -228,12 +228,13 @@ class AccountController extends Controller } /** + * @param Request $request * @param JournalCollectorInterface $collector * @param Account $account * - * @return View + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View */ - public function show(JournalCollectorInterface $collector, Account $account) + public function show(Request $request, JournalCollectorInterface $collector, Account $account) { if ($account->accountType->type === AccountType::INITIAL_BALANCE) { return $this->redirectToOriginalAccount($account); @@ -244,7 +245,7 @@ class AccountController extends Controller $range = Preferences::get('viewRange', '1M')->data; $start = session('start', Navigation::startOfPeriod(new Carbon, $range)); $end = session('end', Navigation::endOfPeriod(new Carbon, $range)); - $page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page')); + $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); $chartUri = route('chart.account.single', [$account->id]); @@ -260,20 +261,22 @@ class AccountController extends Controller } /** + * @param Request $request * @param ARI $repository * @param Account $account * * @return View */ - public function showAll(AccountRepositoryInterface $repository, Account $account) + public function showAll(Request $request, AccountRepositoryInterface $repository, Account $account) { $subTitle = sprintf('%s (%s)', $account->name, strtolower(trans('firefly.everything'))); - $page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page')); + $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); $chartUri = route('chart.account.all', [$account->id]); // replace with journal collector: - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page); $journals = $collector->getPaginatedJournals(); $journals->setPath('accounts/show/' . $account->id . '/all'); @@ -287,24 +290,26 @@ class AccountController extends Controller } /** + * @param Request $request * @param Account $account * @param string $date * * @return View */ - public function showByDate(Account $account, string $date) + public function showByDate(Request $request, Account $account, string $date) { $carbon = new Carbon($date); $range = Preferences::get('viewRange', '1M')->data; $start = Navigation::startOfPeriod($carbon, $range); $end = Navigation::endOfPeriod($carbon, $range); $subTitle = $account->name . ' (' . Navigation::periodShow($start, $range) . ')'; - $page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page')); + $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); $chartUri = route('chart.account.period', [$account->id, $carbon->format('Y-m-d')]); // replace with journal collector: - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page); $journals = $collector->getPaginatedJournals(); $journals->setPath('accounts/show/' . $account->id . '/' . $date); @@ -335,7 +340,7 @@ class AccountController extends Controller Preferences::set('frontPageAccounts', $frontPage); } - if (intval(Input::get('create_another')) === 1) { + if (intval($request->get('create_another')) === 1) { // set value so create routine will not overwrite URL: Session::put('accounts.create.fromStore', true); @@ -361,7 +366,7 @@ class AccountController extends Controller Session::flash('success', strval(trans('firefly.updated_account', ['name' => $account->name]))); Preferences::mark(); - if (intval(Input::get('return_to_edit')) === 1) { + if (intval($request->get('return_to_edit')) === 1) { // set value so edit routine will not overwrite URL: Session::put('accounts.edit.fromUpdate', true); diff --git a/app/Http/Controllers/Admin/ConfigurationController.php b/app/Http/Controllers/Admin/ConfigurationController.php index 7d3a8bd3b0..909d33ae62 100644 --- a/app/Http/Controllers/Admin/ConfigurationController.php +++ b/app/Http/Controllers/Admin/ConfigurationController.php @@ -58,23 +58,13 @@ class ConfigurationController extends Controller // all available configuration and their default value in case // they don't exist yet. - $singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; - $mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data; - $isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data; - $siteOwner = env('SITE_OWNER'); - - // email settings: - $sendErrorMessage = [ - 'mail_for_lockout' => FireflyConfig::get('mail_for_lockout', config('firefly.configuration.mail_for_lockout'))->data, - 'mail_for_blocked_domain' => FireflyConfig::get('mail_for_blocked_domain', config('firefly.configuration.mail_for_blocked_domain'))->data, - 'mail_for_blocked_email' => FireflyConfig::get('mail_for_blocked_email', config('firefly.configuration.mail_for_blocked_email'))->data, - 'mail_for_bad_login' => FireflyConfig::get('mail_for_bad_login', config('firefly.configuration.mail_for_bad_login'))->data, - 'mail_for_blocked_login' => FireflyConfig::get('mail_for_blocked_login', config('firefly.configuration.mail_for_blocked_login'))->data, - ]; + $singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; + $isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data; + $siteOwner = env('SITE_OWNER'); return view( 'admin.configuration.index', - compact('subTitle', 'subTitleIcon', 'singleUserMode', 'mustConfirmAccount', 'isDemoSite', 'sendErrorMessage', 'siteOwner') + compact('subTitle', 'subTitleIcon', 'singleUserMode', 'isDemoSite', 'siteOwner') ); } @@ -91,16 +81,8 @@ class ConfigurationController extends Controller // store config values FireflyConfig::set('single_user_mode', $data['single_user_mode']); - FireflyConfig::set('must_confirm_account', $data['must_confirm_account']); FireflyConfig::set('is_demo_site', $data['is_demo_site']); - // email settings - FireflyConfig::set('mail_for_lockout', $data['mail_for_lockout']); - FireflyConfig::set('mail_for_blocked_domain', $data['mail_for_blocked_domain']); - FireflyConfig::set('mail_for_blocked_email', $data['mail_for_blocked_email']); - FireflyConfig::set('mail_for_bad_login', $data['mail_for_bad_login']); - FireflyConfig::set('mail_for_blocked_login', $data['mail_for_blocked_login']); - // flash message Session::flash('success', strval(trans('firefly.configuration_updated'))); Preferences::mark(); diff --git a/app/Http/Controllers/Admin/DomainController.php b/app/Http/Controllers/Admin/DomainController.php deleted file mode 100644 index 2115d5816d..0000000000 --- a/app/Http/Controllers/Admin/DomainController.php +++ /dev/null @@ -1,140 +0,0 @@ -data; - - // known domains - $knownDomains = $this->getKnownDomains(); - - return view('admin.domains.index', compact('title', 'mainTitleIcon', 'knownDomains', 'subTitle', 'subTitleIcon', 'domains')); - } - - - /** - * @param Request $request - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function manual(Request $request) - { - if (strlen($request->get('domain')) === 0) { - Session::flash('error', trans('firefly.no_domain_filled_in')); - - return redirect(route('admin.users.domains')); - } - - $domain = strtolower($request->get('domain')); - $blocked = FireflyConfig::get('blocked-domains', [])->data; - - if (in_array($domain, $blocked)) { - Session::flash('error', trans('firefly.domain_already_blocked', ['domain' => $domain])); - - return redirect(route('admin.users.domains')); - } - $blocked[] = $domain; - FireflyConfig::set('blocked-domains', $blocked); - - Session::flash('success', trans('firefly.domain_is_now_blocked', ['domain' => $domain])); - - return redirect(route('admin.users.domains')); - } - - /** - * @param string $domain - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function toggleDomain(string $domain) - { - $domain = strtolower($domain); - $blocked = FireflyConfig::get('blocked-domains', [])->data; - - if (in_array($domain, $blocked)) { - $key = array_search($domain, $blocked); - unset($blocked[$key]); - sort($blocked); - - FireflyConfig::set('blocked-domains', $blocked); - Session::flash('message', trans('firefly.domain_now_unblocked', ['domain' => $domain])); - - - return redirect(route('admin.users.domains')); - - } - - $blocked[] = $domain; - - FireflyConfig::set('blocked-domains', $blocked); - Session::flash('message', trans('firefly.domain_now_blocked', ['domain' => $domain])); - - return redirect(route('admin.users.domains')); - } - - /** - * @return array - */ - private function getKnownDomains(): array - { - /** @var UserRepositoryInterface $repository */ - $repository = app(UserRepositoryInterface::class); - $users = $repository->all(); - $set = []; - $filtered = []; - /** @var User $user */ - foreach ($users as $user) { - $email = $user->email; - $parts = explode('@', $email); - $set[] = strtolower($parts[1]); - } - $set = array_unique($set); - // filter for already banned domains: - $blocked = FireflyConfig::get('blocked-domains', [])->data; - - foreach ($set as $domain) { - // in the block array? ignore it. - if (!in_array($domain, $blocked)) { - $filtered[] = $domain; - } - } - - return $filtered; - } -} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index cab96f62a2..9fcb8b9a99 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -14,7 +14,6 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers\Admin; -use FireflyConfig; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\UserFormRequest; use FireflyIII\Repositories\User\UserRepositoryInterface; @@ -81,30 +80,20 @@ class UserController extends Controller */ public function index(UserRepositoryInterface $repository) { - $subTitle = strval(trans('firefly.user_administration')); - $subTitleIcon = 'fa-users'; - $mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data; - $users = $repository->all(); + $subTitle = strval(trans('firefly.user_administration')); + $subTitleIcon = 'fa-users'; + $users = $repository->all(); // add meta stuff. $users->each( - function (User $user) use ($mustConfirmAccount) { - $list = ['user_confirmed', 'twoFactorAuthEnabled', 'twoFactorAuthSecret', 'registration_ip_address', 'confirmation_ip_address']; - $preferences = Preferences::getArrayForUser($user, $list); - - $user->activated = true; - if (!($preferences['user_confirmed'] === true) && $mustConfirmAccount === true) { - $user->activated = false; - } - + function (User $user) { + $list = ['twoFactorAuthEnabled', 'twoFactorAuthSecret']; + $preferences = Preferences::getArrayForUser($user, $list); $user->isAdmin = $user->hasRole('owner'); $is2faEnabled = $preferences['twoFactorAuthEnabled'] === true; $has2faSecret = !is_null($preferences['twoFactorAuthSecret']); - $user->has2FA = false; - if ($is2faEnabled && $has2faSecret) { - $user->has2FA = true; - } - $user->prefs = $preferences; + $user->has2FA = ($is2faEnabled && $has2faSecret) ? true : false; + $user->prefs = $preferences; } ); @@ -125,37 +114,12 @@ class UserController extends Controller $mainTitleIcon = 'fa-hand-spock-o'; $subTitle = strval(trans('firefly.single_user_administration', ['email' => $user->email])); $subTitleIcon = 'fa-user'; - - // get IP info: - $defaultIp = '0.0.0.0'; - $regPref = Preferences::getForUser($user, 'registration_ip_address'); - $registration = $defaultIp; - $conPref = Preferences::getForUser($user, 'confirmation_ip_address'); - $confirmation = $defaultIp; - if (!is_null($regPref)) { - $registration = $regPref->data; - } - if (!is_null($conPref)) { - $confirmation = $conPref->data; - } - - $registrationHost = ''; - $confirmationHost = ''; - - if ($registration != $defaultIp) { - $registrationHost = gethostbyaddr($registration); - } - if ($confirmation != $defaultIp) { - $confirmationHost = gethostbyaddr($confirmation); - } - - $information = $repository->getUserData($user); + $information = $repository->getUserData($user); return view( 'admin.users.show', compact( - 'title', 'mainTitleIcon', 'subTitle', 'subTitleIcon', 'information', - 'user', 'registration', 'confirmation', 'registrationHost', 'confirmationHost' + 'title', 'mainTitleIcon', 'subTitle', 'subTitleIcon', 'information', 'user' ) ); } diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php index 8cf513f267..0b5d09d776 100644 --- a/app/Http/Controllers/AttachmentController.php +++ b/app/Http/Controllers/AttachmentController.php @@ -13,24 +13,22 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers; -use Crypt; use File; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Requests\AttachmentFormRequest; use FireflyIII\Models\Attachment; use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; -use Input; -use Log; use Preferences; use Response; use Session; -use Storage; use URL; use View; /** * Class AttachmentController * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) // it's 13. + * * @package FireflyIII\Http\Controllers */ class AttachmentController extends Controller @@ -57,7 +55,7 @@ class AttachmentController extends Controller /** * @param Attachment $attachment * - * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory + * @return View */ public function delete(Attachment $attachment) { @@ -101,9 +99,6 @@ class AttachmentController extends Controller $content = $repository->getContent($attachment); $quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\')); - - Log::debug('Send file to user', ['file' => $quoted, 'size' => strlen($content)]); - return response($content, 200) ->header('Content-Description', 'File Transfer') ->header('Content-Type', 'application/octet-stream') @@ -115,7 +110,6 @@ class AttachmentController extends Controller ->header('Pragma', 'public') ->header('Content-Length', strlen($content)); } - throw new FireflyException('Could not find the indicated attachment. The file is no longer there.'); } @@ -147,7 +141,6 @@ class AttachmentController extends Controller { $image = 'images/page_green.png'; - if ($attachment->mime == 'application/pdf') { $image = 'images/page_white_acrobat.png'; } @@ -174,7 +167,7 @@ class AttachmentController extends Controller Session::flash('success', strval(trans('firefly.attachment_updated', ['name' => $attachment->filename]))); Preferences::mark(); - if (intval(Input::get('return_to_edit')) === 1) { + if (intval($request->get('return_to_edit')) === 1) { // set value so edit routine will not overwrite URL: Session::put('attachments.edit.fromUpdate', true); diff --git a/app/Http/Controllers/Auth/ConfirmationController.php b/app/Http/Controllers/Auth/ConfirmationController.php deleted file mode 100644 index 3221971227..0000000000 --- a/app/Http/Controllers/Auth/ConfirmationController.php +++ /dev/null @@ -1,90 +0,0 @@ -data; - $time = Preferences::get('user_confirmed_last_mail', 0)->data; - $now = time(); - $maxDiff = config('firefly.confirmation_age'); - - if ($database === $code && ($now - $time <= $maxDiff)) { - - // trigger user registration event: - event(new ConfirmedUser(auth()->user(), $request->ip())); - - Preferences::setForUser(auth()->user(), 'user_confirmed', true); - Preferences::setForUser(auth()->user(), 'user_confirmed_confirmed', time()); - Session::flash('success', strval(trans('firefly.account_is_confirmed'))); - - return redirect(route('home')); - } - throw new FireflyException(trans('firefly.invalid_activation_code')); - } - - /** - * @param Request $request - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function resendConfirmation(Request $request) - { - $time = Preferences::get('user_confirmed_last_mail', 0)->data; - $now = time(); - $maxDiff = config('firefly.resend_confirmation'); - $owner = env('SITE_OWNER', 'mail@example.com'); - $view = 'auth.confirmation.no-resent'; - if ($now - $time > $maxDiff) { - event(new ResentConfirmation(auth()->user(), $request->ip())); - $view = 'auth.confirmation.resent'; - } - - return view($view, ['owner' => $owner]); - } - -} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 4eb2782162..c6389159b8 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -14,9 +14,6 @@ namespace FireflyIII\Http\Controllers\Auth; use Config; use FireflyConfig; -use FireflyIII\Events\BlockedBadLogin; -use FireflyIII\Events\BlockedUserLogin; -use FireflyIII\Events\LockedOutUser; use FireflyIII\Http\Controllers\Controller; use FireflyIII\User; use Illuminate\Foundation\Auth\AuthenticatesUsers; @@ -64,8 +61,6 @@ class LoginController extends Controller if ($lockedOut) { $this->fireLockoutEvent($request); - event(new LockedOutUser($request->get('email'), $request->ip())); - return $this->sendLockoutResponse($request); } @@ -76,25 +71,8 @@ class LoginController extends Controller return $this->sendLoginResponse($request); } - // check if user is blocked: - $errorMessage = ''; - /** @var User $foundUser */ - $foundUser = User::where('email', $credentials['email'])->where('blocked', 1)->first(); - if (!is_null($foundUser)) { - // user exists, but is blocked: - $code = strlen(strval($foundUser->blocked_code)) > 0 ? $foundUser->blocked_code : 'general_blocked'; - $errorMessage = strval(trans('firefly.' . $code . '_error', ['email' => $credentials['email']])); - event(new BlockedUserLogin($foundUser, $request->ip())); - } + $errorMessage = $this->getBlockedError($credentials['email']); - // simply a bad login. - if (is_null($foundUser)) { - event(new BlockedBadLogin($credentials['email'], $request->ip())); - } - - // If the login attempt was unsuccessful we will increment the number of attempts - // to login and redirect the user back to the login form. Of course, when this - // user surpasses their maximum number of attempts they will get locked out. if (!$lockedOut) { $this->incrementLoginAttempts($request); } @@ -159,4 +137,24 @@ class LoginController extends Controller ] ); } + + /** + * @param string $email + * + * @return string + */ + private function getBlockedError(string $email): string + { + // check if user is blocked: + $errorMessage = ''; + /** @var User $foundUser */ + $foundUser = User::where('email', $email)->where('blocked', 1)->first(); + if (!is_null($foundUser)) { + // user exists, but is blocked: + $code = strlen(strval($foundUser->blocked_code)) > 0 ? $foundUser->blocked_code : 'general_blocked'; + $errorMessage = strval(trans('firefly.' . $code . '_error', ['email' => $email])); + } + + return $errorMessage; + } } diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php index f49857ba67..06a6fa99b2 100644 --- a/app/Http/Controllers/Auth/PasswordController.php +++ b/app/Http/Controllers/Auth/PasswordController.php @@ -47,6 +47,7 @@ class PasswordController extends Controller /** * Send a reset link to the given user. + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 7 but ok * * @param \Illuminate\Http\Request $request * diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 23deaaf46d..2cf28042ba 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -15,14 +15,11 @@ namespace FireflyIII\Http\Controllers\Auth; use Auth; use Config; use FireflyConfig; -use FireflyIII\Events\BlockedUseOfDomain; -use FireflyIII\Events\BlockedUseOfEmail; use FireflyIII\Events\RegisteredUser; use FireflyIII\Http\Controllers\Controller; use FireflyIII\User; use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Http\Request; -use Log; use Session; use Validator; @@ -75,30 +72,6 @@ class RegisterController extends Controller $this->throwValidationException($request, $validator); } - $data = $request->all(); - $data['password'] = bcrypt($data['password']); - - // is user email domain blocked? - if ($this->isBlockedDomain($data['email'])) { - $validator->getMessageBag()->add('email', (string)trans('validation.invalid_domain')); - - event(new BlockedUseOfDomain($data['email'], $request->ip())); - $this->throwValidationException($request, $validator); - } - - // is user a deleted user? - $hash = hash('sha256', $data['email']); - $configuration = FireflyConfig::get('deleted_users', []); - $set = $configuration->data; - Log::debug(sprintf('Hash of email is %s', $hash)); - Log::debug('Hashes of deleted users: ', $set); - if (in_array($hash, $set)) { - $validator->getMessageBag()->add('email', (string)trans('validation.deleted_user')); - event(new BlockedUseOfEmail($data['email'], $request->ip())); - $this->throwValidationException($request, $validator); - } - - $user = $this->create($request->all()); // trigger user registration event: @@ -126,9 +99,6 @@ class RegisterController extends Controller // is demo site? $isDemoSite = FireflyConfig::get('is_demo_site', Config::get('firefly.configuration.is_demo_site'))->data; - // activate account? - $mustConfirmAccount = FireflyConfig::get('must_confirm_account', Config::get('firefly.configuration.must_confirm_account'))->data; - // is allowed to? $singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data; $userCount = User::count(); @@ -140,7 +110,7 @@ class RegisterController extends Controller $email = $request->old('email'); - return view('auth.register', compact('isDemoSite', 'email', 'mustConfirmAccount')); + return view('auth.register', compact('isDemoSite', 'email')); } /** @@ -176,30 +146,4 @@ class RegisterController extends Controller ] ); } - - /** - * @return array - */ - private function getBlockedDomains() - { - return FireflyConfig::get('blocked-domains', [])->data; - } - - /** - * @param string $email - * - * @return bool - */ - private function isBlockedDomain(string $email) - { - $parts = explode('@', $email); - $blocked = $this->getBlockedDomains(); - - if (isset($parts[1]) && in_array($parts[1], $blocked)) { - return true; - } - - return false; - } - } diff --git a/app/Http/Controllers/Auth/TwoFactorController.php b/app/Http/Controllers/Auth/TwoFactorController.php index ae1af3c066..d48f5906f2 100644 --- a/app/Http/Controllers/Auth/TwoFactorController.php +++ b/app/Http/Controllers/Auth/TwoFactorController.php @@ -76,6 +76,7 @@ class TwoFactorController extends Controller /** * @param TokenFormRequest $request + * @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation. * * @return mixed */ diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php index 5e3b3a09ef..0642dd260b 100644 --- a/app/Http/Controllers/BillController.php +++ b/app/Http/Controllers/BillController.php @@ -14,13 +14,13 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Requests\BillFormRequest; use FireflyIII\Models\Bill; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use Illuminate\Http\Request; use Illuminate\Support\Collection; -use Input; use Preferences; use Session; use URL; @@ -197,24 +197,27 @@ class BillController extends Controller } /** + * @param Request $request * @param BillRepositoryInterface $repository * @param Bill $bill * * @return View */ - public function show(BillRepositoryInterface $repository, Bill $bill) + public function show(Request $request, BillRepositoryInterface $repository, Bill $bill) { /** @var Carbon $date */ $date = session('start'); $year = $date->year; - $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page')); + $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); $yearAverage = $repository->getYearAverage($bill, $date); $overallAverage = $repository->getOverallAverage($bill); // use collector: - $collector = new JournalCollector(auth()->user()); - $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->setPage($page)->setLimit($pageSize); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); + $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->setLimit($pageSize)->setPage($page)->withBudgetInformation() + ->withCategoryInformation(); $journals = $collector->getPaginatedJournals(); $journals->setPath('/bills/show/' . $bill->id); @@ -238,7 +241,7 @@ class BillController extends Controller Session::flash('success', strval(trans('firefly.stored_new_bill', ['name' => e($bill->name)]))); Preferences::mark(); - if (intval(Input::get('create_another')) === 1) { + if (intval($request->get('create_another')) === 1) { // set value so create routine will not overwrite URL: Session::put('bills.create.fromStore', true); @@ -265,7 +268,7 @@ class BillController extends Controller Session::flash('success', strval(trans('firefly.updated_bill', ['name' => e($bill->name)]))); Preferences::mark(); - if (intval(Input::get('return_to_edit')) === 1) { + if (intval($request->get('return_to_edit')) === 1) { // set value so edit routine will not overwrite URL: Session::put('bills.edit.fromUpdate', true); diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 77d6ecb164..77f9f5cd64 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -16,16 +16,16 @@ namespace FireflyIII\Http\Controllers; use Amount; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Requests\BudgetFormRequest; use FireflyIII\Http\Requests\BudgetIncomeRequest; use FireflyIII\Models\AccountType; use FireflyIII\Models\Budget; -use FireflyIII\Models\LimitRepetition; +use FireflyIII\Models\BudgetLimit; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use Illuminate\Http\Request; use Illuminate\Support\Collection; -use Input; use Preferences; use Response; use Session; @@ -64,26 +64,26 @@ class BudgetController extends Controller } /** + * @param Request $request * @param BudgetRepositoryInterface $repository * @param Budget $budget * - * @return \Symfony\Component\HttpFoundation\Response + * @return \Illuminate\Http\JsonResponse */ - public function amount(BudgetRepositoryInterface $repository, Budget $budget) + public function amount(Request $request, BudgetRepositoryInterface $repository, Budget $budget) { - $amount = intval(Input::get('amount')); + $amount = intval($request->get('amount')); /** @var Carbon $start */ $start = session('start', Carbon::now()->startOfMonth()); /** @var Carbon $end */ - $end = session('end', Carbon::now()->endOfMonth()); - $viewRange = Preferences::get('viewRange', '1M')->data; - $limitRepetition = $repository->updateLimitAmount($budget, $start, $end, $viewRange, $amount); + $end = session('end', Carbon::now()->endOfMonth()); + $budgetLimit = $repository->updateLimitAmount($budget, $start, $end, $amount); if ($amount == 0) { - $limitRepetition = null; + $budgetLimit = null; } Preferences::mark(); - return Response::json(['name' => $budget->name, 'repetition' => $limitRepetition ? $limitRepetition->id : 0, 'amount' => $amount]); + return Response::json(['name' => $budget->name, 'limit' => $budgetLimit ? $budgetLimit->id : 0, 'amount' => $amount]); } @@ -189,20 +189,22 @@ class BudgetController extends Controller return view( 'budgets.index', - compact('available', 'periodStart', 'periodEnd', 'budgetInformation', 'defaultCurrency', 'inactive', 'budgets', 'spent', 'budgeted') + compact('available', 'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets', 'spent', 'budgeted') ); } /** + * @param Request $request + * * @return View */ - public function noBudget() + public function noBudget(Request $request) { /** @var Carbon $start */ $start = session('start', Carbon::now()->startOfMonth()); /** @var Carbon $end */ $end = session('end', Carbon::now()->endOfMonth()); - $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page')); + $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); $subTitle = trans( 'firefly.without_budget_between', @@ -210,7 +212,8 @@ class BudgetController extends Controller ); // collector - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutBudget(); $journals = $collector->getPaginatedJournals(); $journals->setPath('/budgets/list/noBudget'); @@ -235,35 +238,37 @@ class BudgetController extends Controller } /** + * @param Request $request * @param BudgetRepositoryInterface $repository * @param AccountRepositoryInterface $accountRepository * @param Budget $budget * * @return View */ - public function show(BudgetRepositoryInterface $repository, AccountRepositoryInterface $accountRepository, Budget $budget) + public function show(Request $request, BudgetRepositoryInterface $repository, AccountRepositoryInterface $accountRepository, Budget $budget) { /** @var Carbon $start */ $start = session('first', Carbon::create()->startOfYear()); $end = new Carbon; - $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page')); + $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]); $repetition = null; // collector: - $collector = new JournalCollector(auth()->user()); - $collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); + $collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page)->withCategoryInformation(); $journals = $collector->getPaginatedJournals(); $journals->setPath('/budgets/show/' . $budget->id); - $set = $budget->limitrepetitions()->orderBy('startdate', 'DESC')->get(); + $set = $repository->getBudgetLimits($budget, $start, $end); $subTitle = e($budget->name); $limits = new Collection(); - /** @var LimitRepetition $entry */ + /** @var BudgetLimit $entry */ foreach ($set as $entry) { - $entry->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $entry->startdate, $entry->enddate); + $entry->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $entry->start_date, $entry->end_date); $limits->push($entry); } @@ -271,15 +276,16 @@ class BudgetController extends Controller } /** - * @param Budget $budget - * @param LimitRepetition $repetition + * @param Request $request + * @param Budget $budget + * @param BudgetLimit $budgetLimit * * @return View * @throws FireflyException */ - public function showByRepetition(Budget $budget, LimitRepetition $repetition) + public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit) { - if ($repetition->budgetLimit->budget->id != $budget->id) { + if ($budgetLimit->budget->id != $budget->id) { throw new FireflyException('This budget limit is not part of this budget.'); } @@ -287,27 +293,30 @@ class BudgetController extends Controller $repository = app(BudgetRepositoryInterface::class); /** @var AccountRepositoryInterface $accountRepository */ $accountRepository = app(AccountRepositoryInterface::class); - $start = $repetition->startdate; - $end = $repetition->enddate; - $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page')); + $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); $subTitle = trans( - 'firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)] + 'firefly.budget_in_period', [ + 'name' => $budget->name, + 'start' => $budgetLimit->start_date->formatLocalized($this->monthAndDayFormat), + 'end' => $budgetLimit->end_date->formatLocalized($this->monthAndDayFormat), + ] ); $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]); - // collector: - $collector = new JournalCollector(auth()->user()); - $collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); + $collector->setAllAssetAccounts()->setRange($budgetLimit->start_date, $budgetLimit->end_date) + ->setBudget($budget)->setLimit($pageSize)->setPage($page)->withCategoryInformation(); $journals = $collector->getPaginatedJournals(); - $journals->setPath('/budgets/show/' . $budget->id . '/' . $repetition->id); + $journals->setPath('/budgets/show/' . $budget->id . '/' . $budgetLimit->id); - $repetition->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $repetition->startdate, $repetition->enddate); - $limits = new Collection([$repetition]); + $budgetLimit->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $budgetLimit->start_date, $budgetLimit->end_date); + $limits = new Collection([$budgetLimit]); - return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle')); + return view('budgets.show', compact('limits', 'budget', 'budgetLimit', 'journals', 'subTitle')); } @@ -325,7 +334,7 @@ class BudgetController extends Controller Session::flash('success', strval(trans('firefly.stored_new_budget', ['name' => e($budget->name)]))); Preferences::mark(); - if (intval(Input::get('create_another')) === 1) { + if (intval($request->get('create_another')) === 1) { // set value so create routine will not overwrite URL: Session::put('budgets.create.fromStore', true); @@ -352,7 +361,7 @@ class BudgetController extends Controller Session::flash('success', strval(trans('firefly.updated_budget', ['name' => e($budget->name)]))); Preferences::mark(); - if (intval(Input::get('return_to_edit')) === 1) { + if (intval($request->get('return_to_edit')) === 1) { // set value so edit routine will not overwrite URL: Session::put('budgets.edit.fromUpdate', true); @@ -399,24 +408,22 @@ class BudgetController extends Controller 'budgeted' => '0', 'currentRep' => false, ]; - $allRepetitions = $this->repository->getAllBudgetLimitRepetitions($start, $end); - $otherRepetitions = new Collection; + $budgetLimits = $this->repository->getBudgetLimits($budget, $start, $end); + $otherLimits = new Collection; - // get all the limit repetitions relevant between start and end and examine them: - /** @var LimitRepetition $repetition */ - foreach ($allRepetitions as $repetition) { - if ($repetition->budget_id == $budget->id) { - if ($repetition->startdate->isSameDay($start) && $repetition->enddate->isSameDay($end) - ) { - $return[$budgetId]['currentRep'] = $repetition; - $return[$budgetId]['budgeted'] = $repetition->amount; - continue; - } - // otherwise it's just one of the many relevant repetitions: - $otherRepetitions->push($repetition); + // get all the budget limits relevant between start and end and examine them: + /** @var BudgetLimit $limit */ + foreach ($budgetLimits as $limit) { + if ($limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end) + ) { + $return[$budgetId]['currentLimit'] = $limit; + $return[$budgetId]['budgeted'] = $limit->amount; + continue; } + // otherwise it's just one of the many relevant repetitions: + $otherLimits->push($limit); } - $return[$budgetId]['otherRepetitions'] = $otherRepetitions; + $return[$budgetId]['otherLimits'] = $otherLimits; } return $return; diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index 4d7a67e64c..b9146890db 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -14,16 +14,15 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\JournalCollector; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Requests\CategoryFormRequest; use FireflyIII\Models\AccountType; use FireflyIII\Models\Category; use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Support\CacheProperties; +use Illuminate\Http\Request; use Illuminate\Support\Collection; -use Input; use Navigation; use Preferences; use Session; @@ -89,13 +88,14 @@ class CategoryController extends Controller return view('categories.delete', compact('category', 'subTitle')); } + /** - * @param CRI $repository - * @param Category $category + * @param CategoryRepositoryInterface $repository + * @param Category $category * - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function destroy(CRI $repository, Category $category) + public function destroy(CategoryRepositoryInterface $repository, Category $category) { $name = $category->name; @@ -136,11 +136,11 @@ class CategoryController extends Controller } /** - * @param CRI $repository + * @param CategoryRepositoryInterface $repository * * @return View */ - public function index(CRI $repository) + public function index(CategoryRepositoryInterface $repository) { $categories = $repository->getCategories(); @@ -164,7 +164,8 @@ class CategoryController extends Controller $end = session('end', Carbon::now()->startOfMonth()); // new collector: - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAllAssetAccounts()->setRange($start, $end)->withoutCategory();//->groupJournals(); $journals = $collector->getJournals(); $subTitle = trans( @@ -176,109 +177,98 @@ class CategoryController extends Controller } /** - * @param CRI $repository - * @param AccountRepositoryInterface $accountRepository - * @param Category $category + * @param Request $request + * @param JournalCollectorInterface $collector + * @param Category $category * * @return View */ - public function show(CRI $repository, AccountRepositoryInterface $accountRepository, Category $category) + public function show(Request $request, JournalCollectorInterface $collector, Category $category) { - $range = Preferences::get('viewRange', '1M')->data; - /** @var Carbon $start */ - $start = session('start', Navigation::startOfPeriod(new Carbon, $range)); - /** @var Carbon $end */ + $range = Preferences::get('viewRange', '1M')->data; + $start = session('start', Navigation::startOfPeriod(new Carbon, $range)); $end = session('end', Navigation::endOfPeriod(new Carbon, $range)); - $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); $hideCategory = true; // used in list. - $page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page')); + $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); $subTitle = $category->name; $subTitleIcon = 'fa-bar-chart'; - // use journal collector - $collector = app(JournalCollectorInterface::class); - $collector->setPage($page)->setLimit($pageSize)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category); + $collector->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category)->withBudgetInformation(); $journals = $collector->getPaginatedJournals(); $journals->setPath('categories/show/' . $category->id); - // oldest transaction in category: + $entries = $this->getGroupedEntries($category); + + return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle', 'subTitleIcon', 'start', 'end')); + } + + /** + * @param Request $request + * @param CategoryRepositoryInterface $repository + * @param Category $category + * + * @return View + */ + public function showAll(Request $request, CategoryRepositoryInterface $repository, Category $category) + { + $range = Preferences::get('viewRange', '1M')->data; $start = $repository->firstUseDate($category); if ($start->year == 1900) { $start = new Carbon; } - $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod($start, $range); - $end = Navigation::endOfX(new Carbon, $range); - $entries = new Collection; + $end = Navigation::endOfPeriod(new Carbon, $range); + $subTitle = $category->name; + $subTitleIcon = 'fa-bar-chart'; + $hideCategory = true; // used in list. + $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); + $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); + $showAll = true; - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty('category-show'); - $cache->addProperty($category->id); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setCategory($category)->withBudgetInformation(); + $journals = $collector->getPaginatedJournals(); + $journals->setPath('categories/show/' . $category->id . '/all'); - - if ($cache->has()) { - $entries = $cache->get(); - - return view('categories.show', compact('category', 'journals', 'entries', 'subTitleIcon', 'hideCategory', 'subTitle')); - } - - - $categoryCollection = new Collection([$category]); - - while ($end >= $start) { - $end = Navigation::startOfPeriod($end, $range); - $currentEnd = Navigation::endOfPeriod($end, $range); - $spent = $repository->spentInPeriod($categoryCollection, $accounts, $end, $currentEnd); - $earned = $repository->earnedInPeriod($categoryCollection, $accounts, $end, $currentEnd); - $dateStr = $end->format('Y-m-d'); - $dateName = Navigation::periodShow($end, $range); - $entries->push([$dateStr, $dateName, $spent, $earned]); - - $end = Navigation::subtractPeriod($end, $range, 1); - - } - $cache->store($entries); - - return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle', 'subTitleIcon')); + return view('categories.show', compact('category', 'journals', 'hideCategory', 'subTitle', 'subTitleIcon', 'start', 'end', 'showAll')); } /** + * @param Request $request * @param Category $category - * @param $date + * @param string $date * * @return View */ - public function showByDate(Category $category, string $date) + public function showByDate(Request $request, Category $category, string $date) { $carbon = new Carbon($date); $range = Preferences::get('viewRange', '1M')->data; $start = Navigation::startOfPeriod($carbon, $range); $end = Navigation::endOfPeriod($carbon, $range); $subTitle = $category->name; + $subTitleIcon = 'fa-bar-chart'; $hideCategory = true; // used in list. - $page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page')); + $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - // new collector: + /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class); - $collector->setPage($page)->setLimit($pageSize)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category); + $collector->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category)->withBudgetInformation(); $journals = $collector->getPaginatedJournals(); $journals->setPath('categories/show/' . $category->id . '/' . $date); - return view('categories.show-by-date', compact('category', 'journals', 'hideCategory', 'subTitle', 'carbon')); + return view('categories.show', compact('category', 'journals', 'hideCategory', 'subTitle', 'subTitleIcon', 'start', 'end')); } /** - * @param CategoryFormRequest $request - * @param CRI $repository + * @param CategoryFormRequest $request + * @param CategoryRepositoryInterface $repository * - * @return \Illuminate\Http\RedirectResponse + * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function store(CategoryFormRequest $request, CRI $repository) + public function store(CategoryFormRequest $request, CategoryRepositoryInterface $repository) { $data = $request->getCategoryData(); $category = $repository->store($data); @@ -286,7 +276,7 @@ class CategoryController extends Controller Session::flash('success', strval(trans('firefly.stored_category', ['name' => e($category->name)]))); Preferences::mark(); - if (intval(Input::get('create_another')) === 1) { + if (intval($request->get('create_another')) === 1) { Session::put('categories.create.fromStore', true); return redirect(route('categories.create'))->withInput(); @@ -297,13 +287,13 @@ class CategoryController extends Controller /** - * @param CategoryFormRequest $request - * @param CRI $repository - * @param Category $category + * @param CategoryFormRequest $request + * @param CategoryRepositoryInterface $repository + * @param Category $category * - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function update(CategoryFormRequest $request, CRI $repository, Category $category) + public function update(CategoryFormRequest $request, CategoryRepositoryInterface $repository, Category $category) { $data = $request->getCategoryData(); $repository->update($category, $data); @@ -311,7 +301,7 @@ class CategoryController extends Controller Session::flash('success', strval(trans('firefly.updated_category', ['name' => e($category->name)]))); Preferences::mark(); - if (intval(Input::get('return_to_edit')) === 1) { + if (intval($request->get('return_to_edit')) === 1) { Session::put('categories.edit.fromUpdate', true); return redirect(route('categories.edit', [$category->id])); @@ -322,4 +312,48 @@ class CategoryController extends Controller } + /** + * @param Category $category + * + * @return Collection + */ + private function getGroupedEntries(Category $category): Collection + { + $repository = app(CategoryRepositoryInterface::class); + $accountRepository = app(AccountRepositoryInterface::class); + $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + $first = $repository->firstUseDate($category); + if ($first->year == 1900) { + $first = new Carbon; + } + $range = Preferences::get('viewRange', '1M')->data; + $first = Navigation::startOfPeriod($first, $range); + $end = Navigation::endOfX(new Carbon, $range); + $entries = new Collection; + + // properties for entries with their amounts. + $cache = new CacheProperties(); + $cache->addProperty($first); + $cache->addProperty($end); + $cache->addProperty('categories.entries'); + $cache->addProperty($category->id); + + if ($cache->has()) { + return $cache->get(); + } + while ($end >= $first) { + $end = Navigation::startOfPeriod($end, $range); + $currentEnd = Navigation::endOfPeriod($end, $range); + $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $end, $currentEnd); + $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $end, $currentEnd); + $dateStr = $end->format('Y-m-d'); + $dateName = Navigation::periodShow($end, $range); + $entries->push([$dateStr, $dateName, $spent, $earned]); + $end = Navigation::subtractPeriod($end, $range, 1); + } + $cache->store($entries); + + return $entries; + } + } diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 8bf0047e6f..5391d06883 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -128,7 +128,7 @@ class AccountController extends Controller $endBalance = $endBalances[$id] ?? '0'; $diff = bcsub($endBalance, $startBalance); if (bccomp($diff, '0') !== 0) { - $chartData[$account->name] = round($diff, 2); + $chartData[$account->name] = $diff; } } arsort($chartData); @@ -391,7 +391,7 @@ class AccountController extends Controller $diff = bcsub($endBalance, $startBalance); $diff = bcmul($diff, '-1'); if (bccomp($diff, '0') !== 0) { - $chartData[$account->name] = round($diff, 2); + $chartData[$account->name] = $diff; } } @@ -475,11 +475,11 @@ class AccountController extends Controller ]; $currentStart = clone $start; $range = Steam::balanceInRange($account, $start, clone $end); - $previous = round(array_values($range)[0], 2); + $previous = array_values($range)[0]; while ($currentStart <= $end) { $format = $currentStart->format('Y-m-d'); $label = $currentStart->formatLocalized(strval(trans('config.month_and_day'))); - $balance = isset($range[$format]) ? round($range[$format], 2) : $previous; + $balance = isset($range[$format]) ? round($range[$format], 12) : $previous; $previous = $balance; $currentStart->addDay(); $currentSet['entries'][$label] = $balance; diff --git a/app/Http/Controllers/Chart/BillController.php b/app/Http/Controllers/Chart/BillController.php index 26ae9963c5..14588e0720 100644 --- a/app/Http/Controllers/Chart/BillController.php +++ b/app/Http/Controllers/Chart/BillController.php @@ -91,40 +91,24 @@ class BillController extends Controller return Response::json($cache->get()); } - $results = $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->getJournals(); - $results = $results->sortBy( + $results = $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->getJournals(); + $results = $results->sortBy( function (Transaction $transaction) { return $transaction->date->format('U'); } ); - $chartData = [ - [ - 'type' => 'bar', - 'label' => trans('firefly.min-amount'), - 'entries' => [], - ], - [ - 'type' => 'bar', - 'label' => trans('firefly.max-amount'), - 'entries' => [], - ], - [ - 'type' => 'line', - 'label' => trans('firefly.journal-amount'), - 'entries' => [], - ], + ['type' => 'bar', 'label' => trans('firefly.min-amount'), 'entries' => [],], + ['type' => 'bar', 'label' => trans('firefly.max-amount'), 'entries' => [],], + ['type' => 'line', 'label' => trans('firefly.journal-amount'), 'entries' => [],], ]; /** @var Transaction $entry */ foreach ($results as $entry) { - $date = $entry->date->formatLocalized(strval(trans('config.month_and_day'))); - // minimum amount of bill: - $chartData[0]['entries'][$date] = $bill->amount_min; - // maximum amount of bill: - $chartData[1]['entries'][$date] = $bill->amount_max; - // amount of journal: - $chartData[2]['entries'][$date] = bcmul($entry->transaction_amount, '-1'); + $date = $entry->date->formatLocalized(strval(trans('config.month_and_day'))); + $chartData[0]['entries'][$date] = $bill->amount_min; // minimum amount of bill + $chartData[1]['entries'][$date] = $bill->amount_max; // maximum amount of bill + $chartData[2]['entries'][$date] = bcmul($entry->transaction_amount, '-1'); // amount of journal } $data = $this->generator->multiSet($chartData); diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index aa9a737336..0a8fe543cf 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -14,11 +14,12 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Generator\Chart\Basic\GeneratorInterface; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Budget; -use FireflyIII\Models\LimitRepetition; +use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; @@ -31,6 +32,8 @@ use Response; /** * Class BudgetController * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) // can't realy be helped. + * * @package FireflyIII\Http\Controllers\Chart */ class BudgetController extends Controller @@ -39,26 +42,35 @@ class BudgetController extends Controller /** @var GeneratorInterface */ protected $generator; + /** @var BudgetRepositoryInterface */ + protected $repository; + /** * BudgetController constructor. */ public function __construct() { parent::__construct(); - $this->generator = app(GeneratorInterface::class); + + $this->middleware( + function ($request, $next) { + $this->generator = app(GeneratorInterface::class); + $this->repository = app(BudgetRepositoryInterface::class); + + return $next($request); + } + ); } /** - * checked * - * @param BudgetRepositoryInterface $repository - * @param Budget $budget + * @param Budget $budget * * @return \Symfony\Component\HttpFoundation\Response */ - public function budget(BudgetRepositoryInterface $repository, Budget $budget) + public function budget(Budget $budget) { - $first = $repository->firstUseDate($budget); + $first = $this->repository->firstUseDate($budget); $range = Preferences::get('viewRange', '1M')->data; $last = session('end', new Carbon); @@ -73,7 +85,6 @@ class BudgetController extends Controller $final = clone $last; $final->addYears(2); - $budgetCollection = new Collection([$budget]); $last = Navigation::endOfX($last, $range, $final); // not to overshoot. $entries = []; @@ -84,7 +95,7 @@ class BudgetController extends Controller $currentEnd = Navigation::endOfPeriod($first, $range); // sub another day because reasons. $currentEnd->subDay(); - $spent = $repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd); + $spent = $this->repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd); $format = Navigation::periodShow($first, $range); $entries[$format] = bcmul($spent, '-1'); $first = Navigation::addPeriod($first, $range, 0); @@ -100,31 +111,36 @@ class BudgetController extends Controller /** * Shows the amount left in a specific budget limit. * - * @param BudgetRepositoryInterface $repository - * @param Budget $budget - * @param LimitRepetition $repetition + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. + * @param Budget $budget + * @param BudgetLimit $budgetLimit * * @return \Symfony\Component\HttpFoundation\Response + * @throws FireflyException */ - public function budgetLimit(BudgetRepositoryInterface $repository, Budget $budget, LimitRepetition $repetition) + public function budgetLimit(Budget $budget, BudgetLimit $budgetLimit) { - $start = clone $repetition->startdate; - $end = $repetition->enddate; + if ($budgetLimit->budget->id != $budget->id) { + throw new FireflyException('This budget limit is not part of this budget.'); + } + + $start = clone $budgetLimit->start_date; + $end = clone $budgetLimit->end_date; $cache = new CacheProperties(); $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty('chart.budget.budget.limit'); - $cache->addProperty($repetition->id); + $cache->addProperty($budgetLimit->id); if ($cache->has()) { return Response::json($cache->get()); } $entries = []; - $amount = $repetition->amount; + $amount = $budgetLimit->amount; $budgetCollection = new Collection([$budget]); while ($start <= $end) { - $spent = $repository->spentInPeriod($budgetCollection, new Collection, $start, $start); + $spent = $this->repository->spentInPeriod($budgetCollection, new Collection, $start, $start); $amount = bcadd($amount, $spent); $format = $start->formatLocalized(strval(trans('config.month_and_day'))); $entries[$format] = $amount; @@ -139,12 +155,12 @@ class BudgetController extends Controller /** * Shows a budget list with spent/left/overspent. - * - * @param BudgetRepositoryInterface $repository + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) // 46 lines, I'm fine with this. * * @return \Symfony\Component\HttpFoundation\Response */ - public function frontpage(BudgetRepositoryInterface $repository) + public function frontpage() { $start = session('start', Carbon::now()->startOfMonth()); $end = session('end', Carbon::now()->endOfMonth()); @@ -156,58 +172,32 @@ class BudgetController extends Controller if ($cache->has()) { return Response::json($cache->get()); } - $budgets = $repository->getActiveBudgets(); - $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end); - $chartData = [ - [ - 'label' => strval(trans('firefly.spent_in_budget')), - 'entries' => [], - 'type' => 'bar', - ], - [ - 'label' => strval(trans('firefly.left_to_spend')), - 'entries' => [], - 'type' => 'bar', - ], - [ - 'label' => strval(trans('firefly.overspent')), - 'entries' => [], - 'type' => 'bar', - ], + $budgets = $this->repository->getActiveBudgets(); + $chartData = [ + ['label' => strval(trans('firefly.spent_in_budget')), 'entries' => [], 'type' => 'bar',], + ['label' => strval(trans('firefly.left_to_spend')), 'entries' => [], 'type' => 'bar',], + ['label' => strval(trans('firefly.overspent')), 'entries' => [], 'type' => 'bar',], ]; /** @var Budget $budget */ foreach ($budgets as $budget) { // get relevant repetitions: - $reps = $this->filterRepetitions($repetitions, $budget, $start, $end); - - if ($reps->count() === 0) { - $row = $this->spentInPeriodSingle($repository, $budget, $start, $end); - if (bccomp($row['spent'], '0') !== 0 || bccomp($row['repetition_left'], '0') !== 0) { - $chartData[0]['entries'][$row['name']] = bcmul($row['spent'], '-1'); - $chartData[1]['entries'][$row['name']] = $row['repetition_left']; - $chartData[2]['entries'][$row['name']] = bcmul($row['repetition_overspent'], '-1'); - } - continue; + $limits = $this->repository->getBudgetLimits($budget, $start, $end); + $expenses = $this->getExpensesForBudget($limits, $budget, $start, $end); + foreach ($expenses as $name => $row) { + $chartData[0]['entries'][$name] = $row['spent']; + $chartData[1]['entries'][$name] = $row['left']; + $chartData[2]['entries'][$name] = $row['overspent']; } - $rows = $this->spentInPeriodMulti($repository, $budget, $reps); - foreach ($rows as $row) { - if (bccomp($row['spent'], '0') !== 0 || bccomp($row['repetition_left'], '0') !== 0) { - $chartData[0]['entries'][$row['name']] = bcmul($row['spent'], '-1'); - $chartData[1]['entries'][$row['name']] = $row['repetition_left']; - $chartData[2]['entries'][$row['name']] = bcmul($row['repetition_overspent'], '-1'); - } - } - unset($rows, $row); - } // for no budget: - $row = $this->spentInPeriodWithout($start, $end); - if (bccomp($row['repetition_overspent'], '0') !== 0) { - $chartData[0]['entries'][$row['name']] = bcmul($row['spent'], '-1'); - $chartData[1]['entries'][$row['name']] = $row['repetition_left']; - $chartData[2]['entries'][$row['name']] = bcmul($row['repetition_overspent'], '-1'); + $spent = $this->spentInPeriodWithout($start, $end); + $name = strval(trans('firefly.no_budget')); + if (bccomp($spent, '0') !== 0) { + $chartData[0]['entries'][$name] = bcmul($spent, '-1'); + $chartData[1]['entries'][$name] = '0'; + $chartData[2]['entries'][$name] = '0'; } $data = $this->generator->multiSet($chartData); @@ -218,15 +208,16 @@ class BudgetController extends Controller /** - * @param BudgetRepositoryInterface $repository - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. + * + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts * * @return \Illuminate\Http\JsonResponse */ - public function period(BudgetRepositoryInterface $repository, Budget $budget, Collection $accounts, Carbon $start, Carbon $end) + public function period(Budget $budget, Collection $accounts, Carbon $start, Carbon $end) { // chart properties for cache: $cache = new CacheProperties(); @@ -238,56 +229,22 @@ class BudgetController extends Controller if ($cache->has()) { return Response::json($cache->get()); } - - // get the expenses - $budgeted = []; $periods = Navigation::listOfPeriods($start, $end); - $entries = $repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end); - $key = Navigation::preferredCarbonFormat($start, $end); - $range = Navigation::preferredRangeFormat($start, $end); - - // get the budget limits (if any) - $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end); - $current = clone $start; - while ($current < $end) { - $currentStart = Navigation::startOfPeriod($current, $range); - $currentEnd = Navigation::endOfPeriod($current, $range); - $reps = $repetitions->filter( - function (LimitRepetition $repetition) use ($budget, $currentStart, $currentEnd) { - if ($repetition->budget_id === $budget->id && $repetition->startdate >= $currentStart && $repetition->enddate <= $currentEnd) { - return true; - } - - return false; - } - ); - $index = $currentStart->format($key); - $budgeted[$index] = $reps->sum('amount'); - $currentEnd->addDay(); - $current = clone $currentEnd; - } + $entries = $this->repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end); // get the expenses + $budgeted = $this->getBudgetedInPeriod($budget, $start, $end); // join them into one set of data: $chartData = [ - [ - 'label' => strval(trans('firefly.spent')), - 'type' => 'bar', - 'entries' => [], - ], - [ - 'label' => strval(trans('firefly.budgeted')), - 'type' => 'bar', - 'entries' => [], - ], + ['label' => strval(trans('firefly.spent')), 'type' => 'bar', 'entries' => [],], + ['label' => strval(trans('firefly.budgeted')), 'type' => 'bar', 'entries' => [],], ]; foreach (array_keys($periods) as $period) { $label = $periods[$period]; $spent = isset($entries[$budget->id]['entries'][$period]) ? $entries[$budget->id]['entries'][$period] : '0'; $limit = isset($budgeted[$period]) ? $budgeted[$period] : 0; - $chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 2); + $chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12); $chartData[1]['entries'][$label] = $limit; - } $data = $this->generator->multiSet($chartData); $cache->store($data); @@ -296,14 +253,13 @@ class BudgetController extends Controller } /** - * @param BudgetRepositoryInterface $repository - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end * * @return \Illuminate\Http\JsonResponse */ - public function periodNoBudget(BudgetRepositoryInterface $repository, Collection $accounts, Carbon $start, Carbon $end) + public function periodNoBudget(Collection $accounts, Carbon $start, Carbon $end) { // chart properties for cache: $cache = new CacheProperties(); @@ -317,7 +273,7 @@ class BudgetController extends Controller // the expenses: $periods = Navigation::listOfPeriods($start, $end); - $entries = $repository->getNoBudgetPeriodReport($accounts, $start, $end); + $entries = $this->repository->getNoBudgetPeriodReport($accounts, $start, $end); $chartData = []; // join them: @@ -333,98 +289,115 @@ class BudgetController extends Controller } /** - * @param Collection $repetitions + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + private function getBudgetedInPeriod(Budget $budget, Carbon $start, Carbon $end): array + { + $key = Navigation::preferredCarbonFormat($start, $end); + $range = Navigation::preferredRangeFormat($start, $end); + $current = clone $start; + $budgeted = []; + while ($current < $end) { + $currentStart = Navigation::startOfPeriod($current, $range); + $currentEnd = Navigation::endOfPeriod($current, $range); + $budgetLimits = $this->repository->getBudgetLimits($budget, $currentStart, $currentEnd); + $index = $currentStart->format($key); + $budgeted[$index] = $budgetLimits->sum('amount'); + $currentEnd->addDay(); + $current = clone $currentEnd; + } + + return $budgeted; + } + + /** + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but ok. + * + * @param Collection $limits * @param Budget $budget * @param Carbon $start * @param Carbon $end * - * @return Collection - */ - private function filterRepetitions(Collection $repetitions, Budget $budget, Carbon $start, Carbon $end): Collection - { - - return $repetitions->filter( - function (LimitRepetition $repetition) use ($budget, $start, $end) { - if ($repetition->startdate < $end && $repetition->enddate > $start && $repetition->budget_id === $budget->id) { - return true; - } - - return false; - } - ); - } - - /** - * Returns an array with the following values: - * 0 => - * 'name' => name of budget + repetition - * 'repetition_left' => left in budget repetition (always zero) - * 'repetition_overspent' => spent more than budget repetition? (always zero) - * 'spent' => actually spent in period for budget - * 1 => (etc) - * - * @param BudgetRepositoryInterface $repository - * @param Budget $budget - * @param Collection $repetitions - * * @return array */ - private function spentInPeriodMulti(BudgetRepositoryInterface $repository, Budget $budget, Collection $repetitions): array + private function getExpensesForBudget(Collection $limits, Budget $budget, Carbon $start, Carbon $end): array { $return = []; - $format = strval(trans('config.month_and_day')); - $name = $budget->name; - /** @var LimitRepetition $repetition */ - foreach ($repetitions as $repetition) { - $expenses = $repository->spentInPeriod(new Collection([$budget]), new Collection, $repetition->startdate, $repetition->enddate); - - if ($repetitions->count() > 1) { - $name = $budget->name . ' ' . trans( - 'firefly.between_dates', - ['start' => $repetition->startdate->formatLocalized($format), 'end' => $repetition->enddate->formatLocalized($format)] - ); + if ($limits->count() === 0) { + $spent = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end); + if (bccomp($spent, '0') !== 0) { + $return[$budget->name]['spent'] = bcmul($spent, '-1'); + $return[$budget->name]['left'] = 0; + $return[$budget->name]['overspent'] = 0; } - $amount = $repetition->amount; - $left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses); - $spent = $expenses; - $overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0'; - $return[] = [ - 'name' => $name, - 'repetition_left' => $left, - 'repetition_overspent' => $overspent, - 'spent' => $spent, - ]; + + return $return; } + $rows = $this->spentInPeriodMulti($budget, $limits); + foreach ($rows as $name => $row) { + if (bccomp($row['spent'], '0') !== 0 || bccomp($row['left'], '0') !== 0) { + $return[$name]['spent'] = bcmul($row['spent'], '-1'); + $return[$name]['left'] = $row['left']; + $return[$name]['overspent'] = bcmul($row['overspent'], '-1'); + } + } + unset($rows, $row); + return $return; } /** + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. + * * Returns an array with the following values: - * 'name' => name of budget - * 'repetition_left' => left in budget repetition (always zero) - * 'repetition_overspent' => spent more than budget repetition? (always zero) - * 'spent' => actually spent in period for budget + * 0 => + * 'name' => name of budget + repetition + * 'left' => left in budget repetition (always zero) + * 'overspent' => spent more than budget repetition? (always zero) + * 'spent' => actually spent in period for budget + * 1 => (etc) * - * - * @param BudgetRepositoryInterface $repository - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end + * @param Budget $budget + * @param Collection $limits * * @return array */ - private function spentInPeriodSingle(BudgetRepositoryInterface $repository, Budget $budget, Carbon $start, Carbon $end): array + private function spentInPeriodMulti(Budget $budget, Collection $limits): array { - $spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end); - $array = [ - 'name' => $budget->name, - 'repetition_left' => '0', - 'repetition_overspent' => '0', - 'spent' => $spent, - ]; + $return = []; + $format = strval(trans('config.month_and_day')); + $name = $budget->name; + /** @var BudgetLimit $budgetLimit */ + foreach ($limits as $budgetLimit) { + $expenses = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $budgetLimit->start_date, $budgetLimit->end_date); - return $array; + if ($limits->count() > 1) { + $name = $budget->name . ' ' . trans( + 'firefly.between_dates', + [ + 'start' => $budgetLimit->start_date->formatLocalized($format), + 'end' => $budgetLimit->end_date->formatLocalized($format), + ] + ); + } + $amount = $budgetLimit->amount; + $left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses); + $spent = $expenses; + $overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0'; + $return[$name] = [ + 'left' => $left, + 'overspent' => $overspent, + 'spent' => $spent, + ]; + } + + return $return; } /** @@ -437,12 +410,13 @@ class BudgetController extends Controller * @param Carbon $start * @param Carbon $end * - * @return array + * @return string */ - private function spentInPeriodWithout(Carbon $start, Carbon $end): array + private function spentInPeriodWithout(Carbon $start, Carbon $end): string { // collector - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $types = [TransactionType::WITHDRAWAL]; $collector->setAllAssetAccounts()->setTypes($types)->setRange($start, $end)->withoutBudget(); $journals = $collector->getJournals(); @@ -451,13 +425,7 @@ class BudgetController extends Controller foreach ($journals as $entry) { $sum = bcadd($entry->transaction_amount, $sum); } - $array = [ - 'name' => strval(trans('firefly.no_budget')), - 'repetition_left' => '0', - 'repetition_overspent' => $sum, - 'spent' => '0', - ]; - return $array; + return $sum; } } diff --git a/app/Http/Controllers/Chart/BudgetReportController.php b/app/Http/Controllers/Chart/BudgetReportController.php index 5ba5e9dd0e..d131f8a661 100644 --- a/app/Http/Controllers/Chart/BudgetReportController.php +++ b/app/Http/Controllers/Chart/BudgetReportController.php @@ -17,17 +17,17 @@ namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; use FireflyIII\Generator\Chart\Basic\GeneratorInterface; use FireflyIII\Generator\Report\Category\MonthReportGenerator; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Chart\MetaPieChartInterface; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Budget; -use FireflyIII\Models\LimitRepetition; +use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; -use Log; use Navigation; use Response; @@ -76,50 +76,19 @@ class BudgetReportController extends Controller */ public function accountExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end, string $others) { - /** @var bool $others */ - $others = intval($others) === 1; - $cache = new CacheProperties; - $cache->addProperty('chart.budget.report.account-expense'); - $cache->addProperty($accounts); - $cache->addProperty($budgets); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($others); - if ($cache->has()) { - return Response::json($cache->get()); - } - - $names = []; - $set = $this->getExpenses($accounts, $budgets, $start, $end); - $grouped = $this->groupByOpposingAccount($set); - $chartData = []; - $total = '0'; - - foreach ($grouped as $accountId => $amount) { - if (!isset($names[$accountId])) { - $account = $this->accountRepository->find(intval($accountId)); - $names[$accountId] = $account->name; - } - $amount = bcmul($amount, '-1'); - $total = bcadd($total, $amount); - $chartData[$names[$accountId]] = $amount; - } - - // also collect all transactions NOT in these budgets. - if ($others) { - $collector = new JournalCollector(auth()->user()); - $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]); - $journals = $collector->getJournals(); - $sum = strval($journals->sum('transaction_amount')); - $sum = bcmul($sum, '-1'); - $sum = bcsub($sum, $total); - $chartData[strval(trans('firefly.everything_else'))] = $sum; - } - - $data = $this->generator->pieChart($chartData); - $cache->store($data); + /** @var MetaPieChartInterface $helper */ + $helper = app(MetaPieChartInterface::class); + $helper->setAccounts($accounts); + $helper->setBudgets($budgets); + $helper->setUser(auth()->user()); + $helper->setStart($start); + $helper->setEnd($end); + $helper->setCollectOtherObjects(intval($others) === 1); + $chartData = $helper->generate('expense', 'account'); + $data = $this->generator->pieChart($chartData); return Response::json($data); + } /** @@ -133,48 +102,16 @@ class BudgetReportController extends Controller */ public function budgetExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end, string $others) { - /** @var bool $others */ - $others = intval($others) === 1; - $cache = new CacheProperties; - $cache->addProperty('chart.budget.report.budget-expense'); - $cache->addProperty($accounts); - $cache->addProperty($budgets); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($others); - if ($cache->has()) { - return Response::json($cache->get()); - } - - $names = []; - $set = $this->getExpenses($accounts, $budgets, $start, $end); - $grouped = $this->groupByBudget($set); - $total = '0'; - $chartData = []; - - foreach ($grouped as $budgetId => $amount) { - if (!isset($names[$budgetId])) { - $budget = $this->budgetRepository->find(intval($budgetId)); - $names[$budgetId] = $budget->name; - } - $amount = bcmul($amount, '-1'); - $total = bcadd($total, $amount); - $chartData[$names[$budgetId]] = $amount; - } - - // also collect all transactions NOT in these budgets. - if ($others) { - $collector = new JournalCollector(auth()->user()); - $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]); - $journals = $collector->getJournals(); - $sum = strval($journals->sum('transaction_amount')); - $sum = bcmul($sum, '-1'); - $sum = bcsub($sum, $total); - $chartData[strval(trans('firefly.everything_else'))] = $sum; - } - - $data = $this->generator->pieChart($chartData); - $cache->store($data); + /** @var MetaPieChartInterface $helper */ + $helper = app(MetaPieChartInterface::class); + $helper->setAccounts($accounts); + $helper->setBudgets($budgets); + $helper->setUser(auth()->user()); + $helper->setStart($start); + $helper->setEnd($end); + $helper->setCollectOtherObjects(intval($others) === 1); + $chartData = $helper->generate('expense', 'budget'); + $data = $this->generator->pieChart($chartData); return Response::json($data); } @@ -204,7 +141,6 @@ class BudgetReportController extends Controller $function = Navigation::preferredEndOfPeriod($start, $end); $chartData = []; $currentStart = clone $start; - $limits = $repository->getAllBudgetLimitRepetitions($start, $end); // also for ALL budgets. // prep chart data: foreach ($budgets as $budget) { @@ -229,8 +165,9 @@ class BudgetReportController extends Controller 'entries' => [], ]; } - $sumOfExpenses = []; - $leftOfLimits = []; + $allBudgetLimits = $repository->getAllBudgetLimits($start, $end); + $sumOfExpenses = []; + $leftOfLimits = []; while ($currentStart < $end) { $currentEnd = clone $currentStart; $currentEnd = $currentEnd->$function(); @@ -239,20 +176,20 @@ class BudgetReportController extends Controller /** @var Budget $budget */ foreach ($budgets as $budget) { + // get budget limit(s) for this period): + $budgetLimits = $this->filterBudgetLimits($allBudgetLimits, $budget, $currentStart, $currentEnd); $currentExpenses = $expenses[$budget->id] ?? '0'; $sumOfExpenses[$budget->id] = $sumOfExpenses[$budget->id] ?? '0'; $sumOfExpenses[$budget->id] = bcadd($currentExpenses, $sumOfExpenses[$budget->id]); - $chartData[$budget->id]['entries'][$label] = round(bcmul($currentExpenses, '-1'), 2); - $chartData[$budget->id . '-sum']['entries'][$label] = round(bcmul($sumOfExpenses[$budget->id], '-1'), 2); + $chartData[$budget->id]['entries'][$label] = bcmul($currentExpenses, '-1'); + $chartData[$budget->id . '-sum']['entries'][$label] = bcmul($sumOfExpenses[$budget->id], '-1'); - $limit = $this->filterLimits($limits, $budget, $currentStart); - if (!is_null($limit->id)) { - $leftOfLimits[$limit->id] = $leftOfLimits[$limit->id] ?? strval($limit->amount); - $leftOfLimits[$limit->id] = bcadd($leftOfLimits[$limit->id], $currentExpenses); - $chartData[$budget->id . '-left']['entries'][$label] = round($leftOfLimits[$limit->id], 2); + if (count($budgetLimits) > 0) { + $budgetLimitId = $budgetLimits->first()->id; + $leftOfLimits[$budgetLimitId] = $leftOfLimits[$budgetLimitId] ?? strval($budgetLimits->sum('amount')); + $leftOfLimits[$budgetLimitId] = bcadd($leftOfLimits[$budgetLimitId], $currentExpenses); + $chartData[$budget->id . '-left']['entries'][$label] = $leftOfLimits[$budgetLimitId]; } - - } $currentStart = clone $currentEnd; $currentStart->addDay(); @@ -265,44 +202,32 @@ class BudgetReportController extends Controller } /** - * @param $limits - * @param $budget - * @param $currentStart + * Returns the budget limits belonging to the given budget and valid on the given day. * - * @return LimitRepetition + * @param Collection $budgetLimits + * @param Carbon $start + * @param Carbon $end + * + * @return Collection */ - private function filterLimits(Collection $limits, Budget $budget, Carbon $date): LimitRepetition + private function filterBudgetLimits(Collection $budgetLimits, Budget $budget, Carbon $start, Carbon $end): Collection { - Log::debug(sprintf('Start of filterLimits with %d limits.', $limits->count())); - $filtered = $limits->filter( - function (LimitRepetition $limit) use ($budget, $date) { - if ($limit->budget_id !== $budget->id) { - Log::debug(sprintf('LimitRepetition has budget #%d but expecting #%d', $limit->budget_id, $budget->id)); - - return false; - } - if ($date < $limit->startdate || $date > $limit->enddate) { - Log::debug( - sprintf( - 'Date %s is not between %s and %s', - $date->format('Y-m-d'), $limit->startdate->format('Y-m-d'), $limit->enddate->format('Y-m-d') - ) - ); - - return false; + $set = $budgetLimits->filter( + function (BudgetLimit $budgetLimit) use ($budget, $start, $end) { + if ($budgetLimit->budget_id === $budget->id + && $budgetLimit->start_date->lte($start) // start of budget limit is on or before start + && $budgetLimit->end_date->gte($end) // end of budget limit is on or after end + ) { + return $budgetLimit; } - return $limit; + return false; } ); - if ($filtered->count() === 1) { - return $filtered->first(); - } - return new LimitRepetition; + return $set; } - /** * @param Collection $accounts * @param Collection $budgets @@ -313,7 +238,8 @@ class BudgetReportController extends Controller */ private function getExpenses(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end): Collection { - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) ->setBudgets($budgets)->withOpposingAccount()->disableFilter(); $accountIds = $accounts->pluck('id')->toArray(); diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index c0ff85e2e3..6ba325636a 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -65,7 +65,12 @@ class CategoryController extends Controller return Response::json($cache->get()); } - $start = $repository->firstUseDate($category); + $start = $repository->firstUseDate($category); + + if ($start->year == 1900) { + $start = new Carbon; + } + $range = Preferences::get('viewRange', '1M')->data; $start = Navigation::startOfPeriod($start, $range); $end = new Carbon; diff --git a/app/Http/Controllers/Chart/CategoryReportController.php b/app/Http/Controllers/Chart/CategoryReportController.php index be76c938a4..e156becd7e 100644 --- a/app/Http/Controllers/Chart/CategoryReportController.php +++ b/app/Http/Controllers/Chart/CategoryReportController.php @@ -17,7 +17,8 @@ namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; use FireflyIII\Generator\Chart\Basic\GeneratorInterface; use FireflyIII\Generator\Report\Category\MonthReportGenerator; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Chart\MetaPieChartInterface; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Category; use FireflyIII\Models\Transaction; @@ -75,48 +76,16 @@ class CategoryReportController extends Controller */ public function accountExpense(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others) { - /** @var bool $others */ - $others = intval($others) === 1; - $cache = new CacheProperties; - $cache->addProperty('chart.category.report.account-expense'); - $cache->addProperty($accounts); - $cache->addProperty($categories); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($others); - if ($cache->has()) { - return Response::json($cache->get()); - } - - $names = []; - $set = $this->getExpenses($accounts, $categories, $start, $end); - $grouped = $this->groupByOpposingAccount($set); - $chartData = []; - $total = '0'; - - foreach ($grouped as $accountId => $amount) { - if (!isset($names[$accountId])) { - $account = $this->accountRepository->find(intval($accountId)); - $names[$accountId] = $account->name; - } - $amount = bcmul($amount, '-1'); - $total = bcadd($total, $amount); - $chartData[$names[$accountId]] = $amount; - } - - // also collect all transactions NOT in these categories. - if ($others) { - $collector = new JournalCollector(auth()->user()); - $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]); - $journals = $collector->getJournals(); - $sum = strval($journals->sum('transaction_amount')); - $sum = bcmul($sum, '-1'); - $sum = bcsub($sum, $total); - $chartData[strval(trans('firefly.everything_else'))] = $sum; - } - - $data = $this->generator->pieChart($chartData); - $cache->store($data); + /** @var MetaPieChartInterface $helper */ + $helper = app(MetaPieChartInterface::class); + $helper->setAccounts($accounts); + $helper->setCategories($categories); + $helper->setUser(auth()->user()); + $helper->setStart($start); + $helper->setEnd($end); + $helper->setCollectOtherObjects(intval($others) === 1); + $chartData = $helper->generate('expense', 'account'); + $data = $this->generator->pieChart($chartData); return Response::json($data); } @@ -132,47 +101,16 @@ class CategoryReportController extends Controller */ public function accountIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others) { - /** @var bool $others */ - $others = intval($others) === 1; - $cache = new CacheProperties; - $cache->addProperty('chart.category.report.account-income'); - $cache->addProperty($accounts); - $cache->addProperty($categories); - $cache->addProperty($start); - $cache->addProperty($others); - $cache->addProperty($end); - if ($cache->has()) { - return Response::json($cache->get()); - } - - - $names = []; - $set = $this->getIncome($accounts, $categories, $start, $end); - $grouped = $this->groupByOpposingAccount($set); - $chartData = []; - $total = '0'; - - foreach ($grouped as $accountId => $amount) { - if (!isset($names[$accountId])) { - $account = $this->accountRepository->find(intval($accountId)); - $names[$accountId] = $account->name; - } - $total = bcadd($total, $amount); - $chartData[$names[$accountId]] = $amount; - } - - // also collect others? - if ($others) { - $collector = new JournalCollector(auth()->user()); - $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]); - $journals = $collector->getJournals(); - $sum = strval($journals->sum('transaction_amount')); - $sum = bcsub($sum, $total); - $chartData[strval(trans('firefly.everything_else'))] = $sum; - } - - $data = $this->generator->pieChart($chartData); - $cache->store($data); + /** @var MetaPieChartInterface $helper */ + $helper = app(MetaPieChartInterface::class); + $helper->setAccounts($accounts); + $helper->setCategories($categories); + $helper->setUser(auth()->user()); + $helper->setStart($start); + $helper->setEnd($end); + $helper->setCollectOtherObjects(intval($others) === 1); + $chartData = $helper->generate('income', 'account'); + $data = $this->generator->pieChart($chartData); return Response::json($data); } @@ -188,48 +126,16 @@ class CategoryReportController extends Controller */ public function categoryExpense(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others) { - /** @var bool $others */ - $others = intval($others) === 1; - $cache = new CacheProperties; - $cache->addProperty('chart.category.report.category-expense'); - $cache->addProperty($accounts); - $cache->addProperty($categories); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($others); - if ($cache->has()) { - return Response::json($cache->get()); - } - - $names = []; - $set = $this->getExpenses($accounts, $categories, $start, $end); - $grouped = $this->groupByCategory($set); - $total = '0'; - $chartData = []; - - foreach ($grouped as $categoryId => $amount) { - if (!isset($names[$categoryId])) { - $category = $this->categoryRepository->find(intval($categoryId)); - $names[$categoryId] = $category->name; - } - $amount = bcmul($amount, '-1'); - $total = bcadd($total, $amount); - $chartData[$names[$categoryId]] = $amount; - } - - // also collect all transactions NOT in these categories. - if ($others) { - $collector = new JournalCollector(auth()->user()); - $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]); - $journals = $collector->getJournals(); - $sum = strval($journals->sum('transaction_amount')); - $sum = bcmul($sum, '-1'); - $sum = bcsub($sum, $total); - $chartData[strval(trans('firefly.everything_else'))] = $sum; - } - - $data = $this->generator->pieChart($chartData); - $cache->store($data); + /** @var MetaPieChartInterface $helper */ + $helper = app(MetaPieChartInterface::class); + $helper->setAccounts($accounts); + $helper->setCategories($categories); + $helper->setUser(auth()->user()); + $helper->setStart($start); + $helper->setEnd($end); + $helper->setCollectOtherObjects(intval($others) === 1); + $chartData = $helper->generate('expense', 'category'); + $data = $this->generator->pieChart($chartData); return Response::json($data); } @@ -245,45 +151,17 @@ class CategoryReportController extends Controller */ public function categoryIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others) { - /** @var bool $others */ - $others = intval($others) === 1; - $cache = new CacheProperties; - $cache->addProperty('chart.category.report.category-income'); - $cache->addProperty($accounts); - $cache->addProperty($categories); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($others); - if ($cache->has()) { - return Response::json($cache->get()); - } - $names = []; - $set = $this->getIncome($accounts, $categories, $start, $end); - $grouped = $this->groupByCategory($set); - $total = '0'; - $chartData = []; - - foreach ($grouped as $categoryId => $amount) { - if (!isset($names[$categoryId])) { - $category = $this->categoryRepository->find(intval($categoryId)); - $names[$categoryId] = $category->name; - } - $total = bcadd($total, $amount); - $chartData[$names[$categoryId]] = $amount; - } - - if ($others) { - $collector = new JournalCollector(auth()->user()); - $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]); - $journals = $collector->getJournals(); - $sum = strval($journals->sum('transaction_amount')); - $sum = bcsub($sum, $total); - $chartData[strval(trans('firefly.everything_else'))] = $sum; - } - - $data = $this->generator->pieChart($chartData); - $cache->store($data); + /** @var MetaPieChartInterface $helper */ + $helper = app(MetaPieChartInterface::class); + $helper->setAccounts($accounts); + $helper->setCategories($categories); + $helper->setUser(auth()->user()); + $helper->setStart($start); + $helper->setEnd($end); + $helper->setCollectOtherObjects(intval($others) === 1); + $chartData = $helper->generate('income', 'category'); + $data = $this->generator->pieChart($chartData); return Response::json($data); } @@ -405,7 +283,8 @@ class CategoryReportController extends Controller */ private function getExpenses(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): Collection { - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) ->setCategories($categories)->withOpposingAccount()->disableFilter(); $accountIds = $accounts->pluck('id')->toArray(); @@ -425,7 +304,8 @@ class CategoryReportController extends Controller */ private function getIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): Collection { - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) ->setCategories($categories)->withOpposingAccount(); $accountIds = $accounts->pluck('id')->toArray(); diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index ade70f5cf9..0904e4bbbc 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -13,6 +13,7 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers; +use FireflyConfig; use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; @@ -23,7 +24,6 @@ use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Routing\Controller as BaseController; use Session; use View; -use FireflyConfig; /** * Class Controller @@ -52,8 +52,8 @@ class Controller extends BaseController View::share('hideTags', false); $isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data; View::share('IS_DEMO_SITE', $isDemoSite); - View::share('DEMO_USERNAME', env('DEMO_USERNAME','')); - View::share('DEMO_PASSWORD', env('DEMO_PASSWORD','')); + View::share('DEMO_USERNAME', env('DEMO_USERNAME', '')); + View::share('DEMO_PASSWORD', env('DEMO_PASSWORD', '')); // translations: diff --git a/app/Http/Controllers/CurrencyController.php b/app/Http/Controllers/CurrencyController.php index c57e86b7ea..2b01c3043c 100644 --- a/app/Http/Controllers/CurrencyController.php +++ b/app/Http/Controllers/CurrencyController.php @@ -17,7 +17,6 @@ use Cache; use FireflyIII\Http\Requests\CurrencyFormRequest; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; -use Input; use Log; use Preferences; use Session; @@ -170,7 +169,7 @@ class CurrencyController extends Controller if (!auth()->user()->hasRole('owner')) { - Session::flash('warning', trans('firefly.ask_site_owner', ['site_owner' => env('SITE_OWNER')])); + Session::flash('info', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')])); } @@ -196,7 +195,7 @@ class CurrencyController extends Controller $currency = $repository->store($data); Session::flash('success', trans('firefly.created_currency', ['name' => $currency->name])); - if (intval(Input::get('create_another')) === 1) { + if (intval($request->get('create_another')) === 1) { Session::put('currencies.create.fromStore', true); return redirect(route('currencies.create'))->withInput(); @@ -225,7 +224,7 @@ class CurrencyController extends Controller Preferences::mark(); - if (intval(Input::get('return_to_edit')) === 1) { + if (intval($request->get('return_to_edit')) === 1) { Session::put('currencies.edit.fromUpdate', true); return redirect(route('currencies.edit', [$currency->id])); diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index f081736c93..7f9d29eb7f 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -26,7 +26,6 @@ use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface; use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface as EJRI; use Preferences; use Response; -use Storage; use View; /** @@ -145,7 +144,7 @@ class ExportController extends Controller 'job' => $job, ]; - $job->change('export_status_make_exporter'); + $jobs->changeStatus($job, 'export_status_make_exporter'); /** @var ProcessorInterface $processor */ $processor = app(ProcessorInterface::class, [$settings]); @@ -153,47 +152,46 @@ class ExportController extends Controller /* * Collect journals: */ - $job->change('export_status_collecting_journals'); + $jobs->changeStatus($job, 'export_status_collecting_journals'); $processor->collectJournals(); - $job->change('export_status_collected_journals'); + $jobs->changeStatus($job, 'export_status_collected_journals'); /* * Transform to exportable entries: */ - $job->change('export_status_converting_to_export_format'); + $jobs->changeStatus($job, 'export_status_converting_to_export_format'); $processor->convertJournals(); - $job->change('export_status_converted_to_export_format'); + $jobs->changeStatus($job, 'export_status_converted_to_export_format'); /* * Transform to (temporary) file: */ - $job->change('export_status_creating_journal_file'); + $jobs->changeStatus($job, 'export_status_creating_journal_file'); $processor->exportJournals(); - $job->change('export_status_created_journal_file'); + $jobs->changeStatus($job, 'export_status_created_journal_file'); /* * Collect attachments, if applicable. */ if ($settings['includeAttachments']) { - $job->change('export_status_collecting_attachments'); + $jobs->changeStatus($job, 'export_status_collecting_attachments'); $processor->collectAttachments(); - $job->change('export_status_collected_attachments'); + $jobs->changeStatus($job, 'export_status_collected_attachments'); } /* * Collect old uploads */ if ($settings['includeOldUploads']) { - $job->change('export_status_collecting_old_uploads'); + $jobs->changeStatus($job, 'export_status_collecting_old_uploads'); $processor->collectOldUploads(); - $job->change('export_status_collected_old_uploads'); + $jobs->changeStatus($job, 'export_status_collected_old_uploads'); } /* * Create ZIP file: */ - $job->change('export_status_creating_zip_file'); + $jobs->changeStatus($job, 'export_status_creating_zip_file'); $processor->createZipFile(); - $job->change('export_status_created_zip_file'); - - $job->change('export_status_finished'); + $jobs->changeStatus($job, 'export_status_created_zip_file'); + $jobs->changeStatus($job, 'export_status_finished'); return Response::json('ok'); } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 1abee2e11b..9263a25937 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -63,7 +63,6 @@ class HomeController extends Controller // a possible problem with the budgets. if ($label === strval(trans('firefly.everything')) || $label === strval(trans('firefly.customRange'))) { $isCustomRange = true; - //Preferences::set('viewRange', 'custom'); Log::debug('Range is now marked as "custom".'); } @@ -175,9 +174,6 @@ class HomeController extends Controller 'logout', 'two-fac', 'lost-two', - 'confirm', - 'resend', - 'do_confirm', // test troutes 'test-flash', 'all-routes', diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index 7c19bde59e..12fc16c3af 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -23,6 +23,7 @@ use FireflyIII\Repositories\Tag\TagRepositoryInterface; use Illuminate\Http\Request; use Log; use Response; +use Session; use SplFileObject; use Storage; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -368,14 +369,33 @@ class ImportController extends Controller $content = $uploaded->fread($uploaded->getSize()); $contentEncrypted = Crypt::encrypt($content); $disk = Storage::disk('upload'); - $disk->put($newName, $contentEncrypted); - Log::debug('Uploaded file', ['name' => $upload->getClientOriginalName(), 'size' => $upload->getSize(), 'mime' => $upload->getClientMimeType()]); + // user is demo user, replace upload with prepared file. + if (auth()->user()->hasRole('demo')) { + $stubsDisk = Storage::disk('stubs'); + $content = $stubsDisk->get('demo-import.csv'); + $contentEncrypted = Crypt::encrypt($content); + $disk->put($newName, $contentEncrypted); + Log::debug('Replaced upload with demo file.'); - // store configuration file's content into the job's configuration - // thing. - // otherwise, leave it empty. - if ($request->files->has('configuration_file')) { + // also set up prepared configuration. + $configuration = json_decode($stubsDisk->get('demo-configuration.json'), true); + $job->configuration = $configuration; + $job->save(); + Log::debug('Set configuration for demo user', $configuration); + + // also flash info + Session::flash('info', trans('demo.import-configure-security')); + } + if (!auth()->user()->hasRole('demo')) { + // user is not demo, process original upload: + $disk->put($newName, $contentEncrypted); + Log::debug('Uploaded file', ['name' => $upload->getClientOriginalName(), 'size' => $upload->getSize(), 'mime' => $upload->getClientMimeType()]); + } + + // store configuration file's content into the job's configuration thing. Otherwise, leave it empty. + // demo user's configuration upload is ignored completely. + if ($request->files->has('configuration_file') && !auth()->user()->hasRole('demo')) { /** @var UploadedFile $configFile */ $configFile = $request->files->get('configuration_file'); Log::debug( @@ -394,6 +414,9 @@ class ImportController extends Controller } } + // if user is demo user, replace config with prepared config: + + return redirect(route('import.configure', [$job->key])); } diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php index 0cf47797f0..d38012e6a2 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/JsonController.php @@ -15,7 +15,7 @@ namespace FireflyIII\Http\Controllers; use Amount; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountTaskerInterface; @@ -23,7 +23,7 @@ use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Support\CacheProperties; -use Input; +use Illuminate\Http\Request; use Preferences; use Response; @@ -43,11 +43,13 @@ class JsonController extends Controller } /** + * @param Request $request + * * @return \Illuminate\Http\JsonResponse */ - public function action() + public function action(Request $request) { - $count = intval(Input::get('count')) > 0 ? intval(Input::get('count')) : 1; + $count = intval($request->get('count')) > 0 ? intval($request->get('count')) : 1; $keys = array_keys(config('firefly.rule-actions')); $actions = []; foreach ($keys as $key) { @@ -269,18 +271,18 @@ class JsonController extends Controller } /** - * @param $what + * @param JournalCollectorInterface $collector + * @param string $what * * @return \Illuminate\Http\JsonResponse */ - public function transactionJournals($what) + public function transactionJournals(JournalCollectorInterface $collector, string $what) { $descriptions = []; $type = config('firefly.transactionTypesByWhat.' . $what); $types = [$type]; // use journal collector instead: - $collector = new JournalCollector(auth()->user()); $collector->setTypes($types)->setLimit(100)->setPage(1); $journals = $collector->getJournals(); foreach ($journals as $j) { @@ -296,11 +298,13 @@ class JsonController extends Controller } /** + * @param Request $request + * * @return \Illuminate\Http\JsonResponse */ - public function trigger() + public function trigger(Request $request) { - $count = intval(Input::get('count')) > 0 ? intval(Input::get('count')) : 1; + $count = intval($request->get('count')) > 0 ? intval($request->get('count')) : 1; $keys = array_keys(config('firefly.rule-triggers')); $triggers = []; foreach ($keys as $key) { diff --git a/app/Http/Controllers/NewUserController.php b/app/Http/Controllers/NewUserController.php index 3adfdc2973..12a9c396a9 100644 --- a/app/Http/Controllers/NewUserController.php +++ b/app/Http/Controllers/NewUserController.php @@ -117,7 +117,7 @@ class NewUserController extends Controller 'virtualBalance' => 0, 'active' => true, 'accountRole' => 'defaultAsset', - 'openingBalance' => round($request->input('bank_balance'), 2), + 'openingBalance' => round($request->input('bank_balance'), 12), 'openingBalanceDate' => new Carbon, 'openingBalanceCurrency' => intval($request->input('amount_currency_id_bank_balance')), ]; @@ -142,7 +142,7 @@ class NewUserController extends Controller 'virtualBalance' => 0, 'active' => true, 'accountRole' => 'savingAsset', - 'openingBalance' => round($request->input('savings_balance'), 2), + 'openingBalance' => round($request->input('savings_balance'), 12), 'openingBalanceDate' => new Carbon, 'openingBalanceCurrency' => intval($request->input('amount_currency_id_savings_balance')), ]; @@ -163,7 +163,7 @@ class NewUserController extends Controller 'name' => 'Credit card', 'iban' => null, 'accountType' => 'asset', - 'virtualBalance' => round($request->get('credit_card_limit'), 2), + 'virtualBalance' => round($request->get('credit_card_limit'), 12), 'active' => true, 'accountRole' => 'ccAsset', 'openingBalance' => null, diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php index 5d494b5985..3279328282 100644 --- a/app/Http/Controllers/PiggyBankController.php +++ b/app/Http/Controllers/PiggyBankController.php @@ -20,8 +20,8 @@ use FireflyIII\Models\AccountType; use FireflyIII\Models\PiggyBank; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use Illuminate\Http\Request; use Illuminate\Support\Collection; -use Input; use Log; use Preferences; use Response; @@ -219,7 +219,7 @@ class PiggyBankController extends Controller $accounts = []; /** @var PiggyBank $piggyBank */ foreach ($piggyBanks as $piggyBank) { - $piggyBank->savedSoFar = round($piggyBank->currentRelevantRep()->currentamount, 2); + $piggyBank->savedSoFar = $piggyBank->currentRelevantRep()->currentamount; $piggyBank->percentage = $piggyBank->savedSoFar != 0 ? intval($piggyBank->savedSoFar / $piggyBank->targetamount * 100) : 0; $piggyBank->leftToSave = bcsub($piggyBank->targetamount, strval($piggyBank->savedSoFar)); $piggyBank->percentage = $piggyBank->percentage > 100 ? 100 : $piggyBank->percentage; @@ -234,7 +234,7 @@ class PiggyBankController extends Controller 'balance' => Steam::balanceIgnoreVirtual($account, $end), 'leftForPiggyBanks' => $piggyBank->leftOnAccount($end), 'sumOfSaved' => strval($piggyBank->savedSoFar), - 'sumOfTargets' => strval(round($piggyBank->targetamount, 2)), + 'sumOfTargets' => $piggyBank->targetamount, 'leftToSave' => $piggyBank->leftToSave, ]; } else { @@ -248,13 +248,14 @@ class PiggyBankController extends Controller } /** + * @param Request $request * @param PiggyBankRepositoryInterface $repository * * @return \Illuminate\Http\JsonResponse */ - public function order(PiggyBankRepositoryInterface $repository) + public function order(Request $request, PiggyBankRepositoryInterface $repository) { - $data = Input::get('order'); + $data = $request->get('order'); // set all users piggy banks to zero: $repository->reset(); @@ -270,22 +271,24 @@ class PiggyBankController extends Controller } /** + * @param Request $request * @param PiggyBankRepositoryInterface $repository * @param PiggyBank $piggyBank * * @return \Illuminate\Http\RedirectResponse */ - public function postAdd(PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) + public function postAdd(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) { - $amount = strval(round(Input::get('amount'), 2)); + $amount = $request->get('amount'); + Log::debug(sprintf('Found amount is %s', $amount)); /** @var Carbon $date */ $date = session('end', Carbon::now()->endOfMonth()); $leftOnAccount = $piggyBank->leftOnAccount($date); $savedSoFar = strval($piggyBank->currentRelevantRep()->currentamount); $leftToSave = bcsub($piggyBank->targetamount, $savedSoFar); - $maxAmount = round(min($leftOnAccount, $leftToSave), 2); + $maxAmount = strval(min(round($leftOnAccount, 12), round($leftToSave, 12))); - if ($amount <= $maxAmount) { + if (bccomp($amount, $maxAmount) <= 0) { $repetition = $piggyBank->currentRelevantRep(); $currentAmount = $repetition->currentamount ?? '0'; $repetition->currentamount = bcadd($currentAmount, $amount); @@ -309,18 +312,19 @@ class PiggyBankController extends Controller } /** + * @param Request $request * @param PiggyBankRepositoryInterface $repository * @param PiggyBank $piggyBank * * @return \Illuminate\Http\RedirectResponse */ - public function postRemove(PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) + public function postRemove(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) { - $amount = strval(round(Input::get('amount'), 2)); + $amount = strval(round($request->get('amount'), 12)); $savedSoFar = $piggyBank->currentRelevantRep()->currentamount; - if ($amount <= $savedSoFar) { + if (bccomp($amount, $savedSoFar) === -1) { $repetition = $piggyBank->currentRelevantRep(); $repetition->currentamount = bcsub($repetition->currentamount, $amount); $repetition->save(); @@ -394,7 +398,7 @@ class PiggyBankController extends Controller Session::flash('success', strval(trans('firefly.stored_piggy_bank', ['name' => e($piggyBank->name)]))); Preferences::mark(); - if (intval(Input::get('create_another')) === 1) { + if (intval($request->get('create_another')) === 1) { Session::put('piggy-banks.create.fromStore', true); return redirect(route('piggy-banks.create'))->withInput(); @@ -420,7 +424,7 @@ class PiggyBankController extends Controller Session::flash('success', strval(trans('firefly.updated_piggy_bank', ['name' => e($piggyBank->name)]))); Preferences::mark(); - if (intval(Input::get('return_to_edit')) === 1) { + if (intval($request->get('return_to_edit')) === 1) { Session::put('piggy-banks.edit.fromUpdate', true); return redirect(route('piggy-banks.edit', [$piggyBank->id])); diff --git a/app/Http/Controllers/Popup/ReportController.php b/app/Http/Controllers/Popup/ReportController.php index 3d730ca105..0f436dc962 100644 --- a/app/Http/Controllers/Popup/ReportController.php +++ b/app/Http/Controllers/Popup/ReportController.php @@ -17,7 +17,7 @@ namespace FireflyIII\Http\Controllers\Popup; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collection\BalanceLine; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; @@ -102,7 +102,8 @@ class ReportController extends Controller switch (true) { case ($role === BalanceLine::ROLE_DEFAULTROLE && !is_null($budget->id)): - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector ->setAccounts(new Collection([$account])) ->setRange($attributes['startDate'], $attributes['endDate']) @@ -112,8 +113,8 @@ class ReportController extends Controller break; case ($role === BalanceLine::ROLE_DEFAULTROLE && is_null($budget->id)): $budget->name = strval(trans('firefly.no_budget')); - // collector - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector ->setAccounts(new Collection([$account])) ->setTypes($types) @@ -122,8 +123,8 @@ class ReportController extends Controller $journals = $collector->getJournals(); break; case ($role === BalanceLine::ROLE_DIFFROLE): - // journals no budget, not corrected by a tag. - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector ->setAccounts(new Collection([$account])) ->setTypes($types) @@ -134,7 +135,7 @@ class ReportController extends Controller $budget->name = strval(trans('firefly.leftUnbalanced')); $journals = $journals->filter( function (Transaction $transaction) { - $tags = $transaction->transactionJournal->tags()->where('tagMode', 'balancingAct')->count(); + $tags = $transaction->transactionJournal->tags()->where('tagMode', 'balancingAct')->count(); if ($tags === 0) { return true; } @@ -167,7 +168,8 @@ class ReportController extends Controller /** @var BudgetRepositoryInterface $repository */ $repository = app(BudgetRepositoryInterface::class); $budget = $repository->find(intval($attributes['budgetId'])); - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector ->setAccounts($attributes['accounts']) @@ -200,8 +202,8 @@ class ReportController extends Controller $repository = app(CategoryRepositoryInterface::class); $category = $repository->find(intval($attributes['categoryId'])); $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; - // get journal collector instead: - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAccounts($attributes['accounts'])->setTypes($types) ->setRange($attributes['startDate'], $attributes['endDate']) ->setCategory($category); @@ -225,9 +227,10 @@ class ReportController extends Controller /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); - $account = $repository->find(intval($attributes['accountId'])); - $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; - $collector = new JournalCollector(auth()->user()); + $account = $repository->find(intval($attributes['accountId'])); + $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])->setTypes($types); $journals = $collector->getJournals(); $report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report @@ -262,7 +265,8 @@ class ReportController extends Controller $repository = app(AccountRepositoryInterface::class); $account = $repository->find(intval($attributes['accountId'])); $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])->setTypes($types); $journals = $collector->getJournals(); $report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php index 93efecf2a8..f4bd94c4b5 100644 --- a/app/Http/Controllers/PreferencesController.php +++ b/app/Http/Controllers/PreferencesController.php @@ -113,6 +113,7 @@ class PreferencesController extends Controller * @param TokenFormRequest $request * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation. */ public function postCode(TokenFormRequest $request) { diff --git a/app/Http/Controllers/Report/OperationsController.php b/app/Http/Controllers/Report/OperationsController.php index a30954ec7b..f1abe0b96e 100644 --- a/app/Http/Controllers/Report/OperationsController.php +++ b/app/Http/Controllers/Report/OperationsController.php @@ -48,8 +48,9 @@ class OperationsController extends Controller if ($cache->has()) { return $cache->get(); } - $expenses = $this->getExpenseReport($start, $end, $accounts); - $result = view('reports.partials.expenses', compact('expenses'))->render(); + $entries = $this->getExpenseReport($start, $end, $accounts); + $type = 'expense-entry'; + $result = view('reports.partials.income-expenses', compact('entries', 'type'))->render(); $cache->store($result); return $result; @@ -74,9 +75,10 @@ class OperationsController extends Controller if ($cache->has()) { return $cache->get(); } - $income = $this->getIncomeReport($start, $end, $accounts); + $entries = $this->getIncomeReport($start, $end, $accounts); + $type = 'income-entry'; + $result = view('reports.partials.income-expenses', compact('entries', 'type'))->render(); - $result = view('reports.partials.income', compact('income'))->render(); $cache->store($result); return $result; @@ -227,15 +229,22 @@ class OperationsController extends Controller $name = $transaction->opposing_account_name; if (!isset($expenses[$opposingId])) { $expenses[$opposingId] = [ - 'id' => $opposingId, - 'name' => $name, - 'sum' => '0', - 'count' => 0, + 'id' => $opposingId, + 'name' => $name, + 'sum' => '0', + 'average' => '0', + 'count' => 0, ]; } $expenses[$opposingId]['sum'] = bcadd($expenses[$opposingId]['sum'], $transaction->transaction_amount); $expenses[$opposingId]['count']++; } + // do averages: + foreach ($expenses as $key => $entry) { + if ($expenses[$key]['count'] > 1) { + $expenses[$key]['average'] = bcdiv($expenses[$key]['sum'], strval($expenses[$key]['count'])); + } + } return $expenses; diff --git a/app/Http/Controllers/RuleController.php b/app/Http/Controllers/RuleController.php index 0216496be6..8e0e466933 100644 --- a/app/Http/Controllers/RuleController.php +++ b/app/Http/Controllers/RuleController.php @@ -23,7 +23,6 @@ use FireflyIII\Repositories\Rule\RuleRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use FireflyIII\Rules\TransactionMatcher; use Illuminate\Http\Request; -use Input; use Preferences; use Response; use Session; @@ -58,11 +57,12 @@ class RuleController extends Controller /** * Create a new rule. It will be stored under the given $ruleGroup. * + * @param Request $request * @param RuleGroup $ruleGroup * * @return View */ - public function create(RuleGroup $ruleGroup) + public function create(Request $request, RuleGroup $ruleGroup) { // count for possible present previous entered triggers/actions. $triggerCount = 0; @@ -73,13 +73,13 @@ class RuleController extends Controller $oldActions = []; // has old input? - if (Input::old()) { + if ($request->old()) { // process old triggers. - $oldTriggers = $this->getPreviousTriggers(); + $oldTriggers = $this->getPreviousTriggers($request); $triggerCount = count($oldTriggers); // process old actions - $oldActions = $this->getPreviousActions(); + $oldActions = $this->getPreviousActions($request); $actionCount = count($oldActions); } @@ -154,12 +154,13 @@ class RuleController extends Controller } /** + * @param Request $request * @param RuleRepositoryInterface $repository * @param Rule $rule * * @return View */ - public function edit(RuleRepositoryInterface $repository, Rule $rule) + public function edit(Request $request, RuleRepositoryInterface $repository, Rule $rule) { $oldTriggers = $this->getCurrentTriggers($rule); $triggerCount = count($oldTriggers); @@ -167,10 +168,10 @@ class RuleController extends Controller $actionCount = count($oldActions); // has old input? - if (Input::old()) { - $oldTriggers = $this->getPreviousTriggers(); + if ($request->old()) { + $oldTriggers = $this->getPreviousTriggers($request); $triggerCount = count($oldTriggers); - $oldActions = $this->getPreviousActions(); + $oldActions = $this->getPreviousActions($request); $actionCount = count($oldActions); } @@ -255,7 +256,7 @@ class RuleController extends Controller Session::flash('success', trans('firefly.stored_new_rule', ['title' => $rule->title])); Preferences::mark(); - if (intval(Input::get('create_another')) === 1) { + if (intval($request->get('create_another')) === 1) { // set value so create routine will not overwrite URL: Session::put('rules.create.fromStore', true); @@ -342,7 +343,7 @@ class RuleController extends Controller Session::flash('success', trans('firefly.updated_rule', ['title' => $rule->title])); Preferences::mark(); - if (intval(Input::get('return_to_edit')) === 1) { + if (intval($request->get('return_to_edit')) === 1) { // set value so edit routine will not overwrite URL: Session::put('rules.edit.fromUpdate', true); @@ -461,22 +462,24 @@ class RuleController extends Controller } /** + * @param Request $request + * * @return array */ - private function getPreviousActions() + private function getPreviousActions(Request $request) { $newIndex = 0; $actions = []; /** @var array $oldActions */ - $oldActions = is_array(Input::old('rule-action')) ? Input::old('rule-action') : []; + $oldActions = is_array($request->old('rule-action')) ? $request->old('rule-action') : []; foreach ($oldActions as $index => $entry) { $count = ($newIndex + 1); - $checked = isset(Input::old('rule-action-stop')[$index]) ? true : false; + $checked = isset($request->old('rule-action-stop')[$index]) ? true : false; $actions[] = view( 'rules.partials.action', [ 'oldTrigger' => $entry, - 'oldValue' => Input::old('rule-action-value')[$index], + 'oldValue' => $request->old('rule-action-value')[$index], 'oldChecked' => $checked, 'count' => $count, ] @@ -488,22 +491,24 @@ class RuleController extends Controller } /** + * @param Request $request + * * @return array */ - private function getPreviousTriggers() + private function getPreviousTriggers(Request $request) { $newIndex = 0; $triggers = []; /** @var array $oldTriggers */ - $oldTriggers = is_array(Input::old('rule-trigger')) ? Input::old('rule-trigger') : []; + $oldTriggers = is_array($request->old('rule-trigger')) ? $request->old('rule-trigger') : []; foreach ($oldTriggers as $index => $entry) { $count = ($newIndex + 1); - $oldChecked = isset(Input::old('rule-trigger-stop')[$index]) ? true : false; + $oldChecked = isset($request->old('rule-trigger-stop')[$index]) ? true : false; $triggers[] = view( 'rules.partials.trigger', [ 'oldTrigger' => $entry, - 'oldValue' => Input::old('rule-trigger-value')[$index], + 'oldValue' => $request->old('rule-trigger-value')[$index], 'oldChecked' => $oldChecked, 'count' => $count, ] diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php index 6f0a136a1d..ee0cb84c26 100644 --- a/app/Http/Controllers/RuleGroupController.php +++ b/app/Http/Controllers/RuleGroupController.php @@ -22,7 +22,7 @@ use FireflyIII\Models\AccountType; use FireflyIII\Models\RuleGroup; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; -use Input; +use Illuminate\Http\Request; use Preferences; use Session; use URL; @@ -94,17 +94,17 @@ class RuleGroupController extends Controller } /** + * @param Request $request * @param RuleGroupRepositoryInterface $repository - * * @param RuleGroup $ruleGroup * - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function destroy(RuleGroupRepositoryInterface $repository, RuleGroup $ruleGroup) + public function destroy(Request $request, RuleGroupRepositoryInterface $repository, RuleGroup $ruleGroup) { $title = $ruleGroup->title; - $moveTo = auth()->user()->ruleGroups()->find(intval(Input::get('move_rules_before_delete'))); + $moveTo = auth()->user()->ruleGroups()->find(intval($request->get('move_rules_before_delete'))); $repository->destroy($ruleGroup, $moveTo); @@ -218,7 +218,7 @@ class RuleGroupController extends Controller Session::flash('success', strval(trans('firefly.created_new_rule_group', ['title' => $ruleGroup->title]))); Preferences::mark(); - if (intval(Input::get('create_another')) === 1) { + if (intval($request->get('create_another')) === 1) { // set value so create routine will not overwrite URL: Session::put('rule-groups.create.fromStore', true); @@ -263,7 +263,7 @@ class RuleGroupController extends Controller Session::flash('success', strval(trans('firefly.updated_rule_group', ['title' => $ruleGroup->title]))); Preferences::mark(); - if (intval(Input::get('return_to_edit')) === 1) { + if (intval($request->get('return_to_edit')) === 1) { // set value so edit routine will not overwrite URL: Session::put('rule-groups.edit.fromUpdate', true); diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index 9ad571c091..c000fd6fd5 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -13,13 +13,13 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Requests\TagFormRequest; use FireflyIII\Models\Tag; use FireflyIII\Models\Transaction; use FireflyIII\Repositories\Tag\TagRepositoryInterface; +use Illuminate\Http\Request; use Illuminate\Support\Collection; -use Input; use Preferences; use Session; use URL; @@ -68,17 +68,20 @@ class TagController extends Controller } /** + * @param Request $request + * * @return View */ - public function create() + public function create(Request $request) { $subTitle = trans('firefly.new_tag'); $subTitleIcon = 'fa-tag'; + $apiKey = env('GOOGLE_MAPS_API_KEY', ''); $preFilled = [ 'tagMode' => 'nothing', ]; - if (!Input::old('tagMode')) { + if (!$request->old('tagMode')) { Session::flash('preFilled', $preFilled); } // put previous url in session if not redirect from store (not "create another"). @@ -89,7 +92,7 @@ class TagController extends Controller Session::flash('gaEventCategory', 'tags'); Session::flash('gaEventAction', 'create'); - return view('tags.create', compact('subTitle', 'subTitleIcon')); + return view('tags.create', compact('subTitle', 'subTitleIcon', 'apiKey')); } /** @@ -136,6 +139,7 @@ class TagController extends Controller { $subTitle = trans('firefly.edit_tag', ['tag' => $tag->tag]); $subTitleIcon = 'fa-tag'; + $apiKey = env('GOOGLE_MAPS_API_KEY', ''); /* * Default tag options (again) @@ -165,7 +169,7 @@ class TagController extends Controller Session::flash('gaEventCategory', 'tags'); Session::flash('gaEventAction', 'edit'); - return view('tags.edit', compact('tag', 'subTitle', 'subTitleIcon', 'tagOptions')); + return view('tags.edit', compact('tag', 'subTitle', 'subTitleIcon', 'tagOptions', 'apiKey')); } /** @@ -206,21 +210,21 @@ class TagController extends Controller } /** - * @param Tag $tag + * @param Request $request + * @param JournalCollectorInterface $collector + * @param Tag $tag * * @return View */ - public function show(Tag $tag) + public function show(Request $request, JournalCollectorInterface $collector, Tag $tag) { $subTitle = $tag->tag; $subTitleIcon = 'fa-tag'; - $page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page')); + $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); // use collector: - // replace with journal collector: - $collector = new JournalCollector(auth()->user()); - $collector->setAllAssetAccounts()->setLimit($pageSize)->setPage($page)->setTag($tag); + $collector->setAllAssetAccounts()->setLimit($pageSize)->setPage($page)->setTag($tag)->withBudgetInformation()->withCategoryInformation(); $journals = $collector->getPaginatedJournals(); $journals->setPath('tags/show/' . $tag->id); @@ -248,7 +252,7 @@ class TagController extends Controller Session::flash('success', strval(trans('firefly.created_tag', ['tag' => e($data['tag'])]))); Preferences::mark(); - if (intval(Input::get('create_another')) === 1) { + if (intval($request->get('create_another')) === 1) { // set value so create routine will not overwrite URL: Session::put('tags.create.fromStore', true); @@ -275,7 +279,7 @@ class TagController extends Controller Session::flash('success', strval(trans('firefly.updated_tag', ['tag' => e($data['tag'])]))); Preferences::mark(); - if (intval(Input::get('return_to_edit')) === 1) { + if (intval($request->get('return_to_edit')) === 1) { // set value so edit routine will not overwrite URL: Session::put('tags.edit.fromUpdate', true); diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index ebeec3b1d3..45f5583b9a 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -217,8 +217,8 @@ class ConvertController extends Controller switch ($joined) { default: throw new FireflyException('Cannot handle ' . $joined); - case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: # one - case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: #six + case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: // one + case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: // six $data = [ 'name' => $data['source_account_revenue'], 'accountType' => 'revenue', @@ -228,14 +228,14 @@ class ConvertController extends Controller ]; $source = $accountRepository->store($data); break; - case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: # two - case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: #five + case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: // two + case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: // five $source = $sourceAccount; break; - case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: # three + case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: // three $source = $destinationAccount; break; - case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: # four + case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: // four $source = $accountRepository->find(intval($data['source_account_asset'])); break; } diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php index 8b234d3e36..5676e902a8 100644 --- a/app/Http/Controllers/Transaction/MassController.php +++ b/app/Http/Controllers/Transaction/MassController.php @@ -214,7 +214,7 @@ class MassController extends Controller 'source_account_name' => $sourceAccountName, 'destination_account_id' => intval($destAccountId), 'destination_account_name' => $destAccountName, - 'amount' => round($request->get('amount')[$journal->id], 4), + 'amount' => round($request->get('amount')[$journal->id], 12), 'currency_id' => intval($request->get('amount_currency_id_amount_' . $journal->id)), 'date' => new Carbon($request->get('date')[$journal->id]), 'interest_date' => $journal->interest_date, diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index 96915dae35..b1133686f3 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -265,8 +265,8 @@ class SingleController extends Controller return redirect(route('transactions.create', [$request->input('what')]))->withInput(); } - - $this->attachments->saveAttachmentsForModel($journal); + $files = $request->hasFile('attachments') ? $request->file('attachments') : null; + $this->attachments->saveAttachmentsForModel($journal, $files); // store the journal only, flash the rest. if (count($this->attachments->getErrors()->get('attachments')) > 0) { @@ -315,7 +315,8 @@ class SingleController extends Controller $data = $request->getJournalData(); $journal = $repository->update($journal, $data); - $this->attachments->saveAttachmentsForModel($journal); + $files = $request->hasFile('attachments') ? $request->file('attachments') : null; + $this->attachments->saveAttachmentsForModel($journal, $files); // flash errors if (count($this->attachments->getErrors()->get('attachments')) > 0) { diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index eb061a862f..7d4cd17761 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -138,9 +138,9 @@ class SplitController extends Controller $data = $this->arrayFromInput($request); $journal = $repository->updateSplitJournal($journal, $data); - + $files = $request->hasFile('attachments') ? $request->file('attachments') : null; // save attachments: - $this->attachments->saveAttachmentsForModel($journal); + $this->attachments->saveAttachmentsForModel($journal, $files); event(new UpdatedTransactionJournal($journal)); // update, get events by date and sort DESC @@ -257,7 +257,7 @@ class SplitController extends Controller 'source_account_name' => $transaction['source_account_name'], 'destination_account_id' => $transaction['destination_account_id'], 'destination_account_name' => $transaction['destination_account_name'], - 'amount' => round($transaction['destination_amount'], 2), + 'amount' => round($transaction['destination_amount'], 12), 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0, 'category' => $transaction['category'], ]; @@ -292,7 +292,7 @@ class SplitController extends Controller 'source_account_name' => $transaction['source_account_name'] ?? '', 'destination_account_id' => $transaction['destination_account_id'] ?? 0, 'destination_account_name' => $transaction['destination_account_name'] ?? '', - 'amount' => round($transaction['amount'] ?? 0, 2), + 'amount' => round($transaction['amount'] ?? 0, 12), 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0, 'category' => $transaction['category'] ?? '', ]; diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 3a1c4415e7..2ec3e8ce5c 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -71,10 +71,10 @@ class TransactionController extends Controller $start = session('start', Navigation::startOfPeriod(new Carbon, $range)); $end = session('end', Navigation::endOfPeriod(new Carbon, $range)); - + /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts(); - $collector->setRange($start, $end); + $collector->setRange($start, $end)->withBudgetInformation()->withCategoryInformation(); // do not filter transfers if $what = transfer. if (!in_array($what, ['transfer', 'transfers'])) { @@ -123,7 +123,7 @@ class TransactionController extends Controller $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); $collector = app(JournalCollectorInterface::class, [auth()->user()]); - $collector->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts(); + $collector->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->withBudgetInformation()->withCategoryInformation(); // do not filter transfers if $what = transfer. if (!in_array($what, ['transfer', 'transfers'])) { @@ -160,9 +160,10 @@ class TransactionController extends Controller Log::debug(sprintf('Transaction index by date will show between %s and %s', $start->format('Y-m-d'), $end->format('Y-m-d'))); + /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts(); - $collector->setRange($start, $end); + $collector->setRange($start, $end)->withBudgetInformation()->withCategoryInformation(); // do not filter transfers if $what = transfer. if (!in_array($what, ['transfer', 'transfers'])) { diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b9f1190f10..e8dc229028 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -17,8 +17,6 @@ use FireflyIII\Http\Middleware\AuthenticateTwoFactor; use FireflyIII\Http\Middleware\Binder; use FireflyIII\Http\Middleware\EncryptCookies; use FireflyIII\Http\Middleware\IsAdmin; -use FireflyIII\Http\Middleware\IsConfirmed; -use FireflyIII\Http\Middleware\IsNotConfirmed; use FireflyIII\Http\Middleware\Range; use FireflyIII\Http\Middleware\RedirectIfAuthenticated; use FireflyIII\Http\Middleware\RedirectIfTwoFactorAuthenticated; @@ -124,7 +122,6 @@ class Kernel extends HttpKernel SubstituteBindings::class, Authenticate::class, AuthenticateTwoFactor::class, - IsNotConfirmed::class, ], // MUST be logged in @@ -153,7 +150,6 @@ class Kernel extends HttpKernel SubstituteBindings::class, Authenticate::class, AuthenticateTwoFactor::class, - IsConfirmed::class, Range::class, Binder::class, ], @@ -171,11 +167,9 @@ class Kernel extends HttpKernel SubstituteBindings::class, Authenticate::class, AuthenticateTwoFactor::class, - IsConfirmed::class, IsAdmin::class, Range::class, Binder::class, - ], diff --git a/app/Http/Middleware/IsAdmin.php b/app/Http/Middleware/IsAdmin.php index 598de92d86..823be2f20d 100644 --- a/app/Http/Middleware/IsAdmin.php +++ b/app/Http/Middleware/IsAdmin.php @@ -26,8 +26,7 @@ use Illuminate\Support\Facades\Auth; class IsAdmin { /** - * Handle an incoming request. User account must be confirmed for this routine to let - * the user pass. + * Handle an incoming request. Must be admin. * * @param \Illuminate\Http\Request $request * @param \Closure $next diff --git a/app/Http/Middleware/IsConfirmed.php b/app/Http/Middleware/IsConfirmed.php deleted file mode 100644 index 1f53e2f41a..0000000000 --- a/app/Http/Middleware/IsConfirmed.php +++ /dev/null @@ -1,67 +0,0 @@ -guest()) { - if ($request->ajax()) { - return response('Unauthorized.', 401); - } - - return redirect()->guest('login'); - } - // must the user be confirmed in the first place? - $confirmPreference = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account')); - $mustConfirmAccount = false; - if (!is_null($confirmPreference)) { - $mustConfirmAccount = $confirmPreference->data; - } - - // user must be logged in, then continue: - $isConfirmed = Preferences::get('user_confirmed', false)->data; - - if ($isConfirmed === false && $mustConfirmAccount === true) { - - // user account is not confirmed, redirect to - // confirmation page: - return redirect(route('confirmation_error')); - } - - return $next($request); - } -} diff --git a/app/Http/Middleware/IsNotConfirmed.php b/app/Http/Middleware/IsNotConfirmed.php deleted file mode 100644 index 0037c00e7a..0000000000 --- a/app/Http/Middleware/IsNotConfirmed.php +++ /dev/null @@ -1,64 +0,0 @@ -guest()) { - if ($request->ajax()) { - return response('Unauthorized.', 401); - } - - return redirect()->guest('login'); - } - // must the user be confirmed in the first place? - $mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data; - Log::debug(sprintf('mustConfirmAccount is %s', $mustConfirmAccount)); - // user must be logged in, then continue: - $isConfirmed = Preferences::get('user_confirmed', false)->data; - Log::debug(sprintf('isConfirmed is %s', $isConfirmed)); - if ($isConfirmed || $mustConfirmAccount === false) { - Log::debug('User is confirmed or user does not have to confirm account. Redirect home.'); - - // user account is confirmed, simply send them home. - return redirect(route('home')); - } - - return $next($request); - } -} diff --git a/app/Http/Middleware/Range.php b/app/Http/Middleware/Range.php index ee9ca3ec22..5e3e119098 100644 --- a/app/Http/Middleware/Range.php +++ b/app/Http/Middleware/Range.php @@ -13,6 +13,7 @@ declare(strict_types = 1); namespace FireflyIII\Http\Middleware; +use Amount; use App; use Carbon\Carbon; use Closure; @@ -109,19 +110,21 @@ class Range $monthFormat = (string)trans('config.month'); $monthAndDayFormat = (string)trans('config.month_and_day'); $dateTimeFormat = (string)trans('config.date_time'); + $defaultCurrency = Amount::getDefaultCurrency(); // change localeconv to a new array: $numberFormatter = numfmt_create($lang, NumberFormatter::CURRENCY); $localeconv = [ 'mon_decimal_point' => $numberFormatter->getSymbol($numberFormatter->getAttribute(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL)), 'mon_thousands_sep' => $numberFormatter->getSymbol($numberFormatter->getAttribute(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL)), - 'frac_digits' => $numberFormatter->getAttribute(NumberFormatter::MAX_FRACTION_DIGITS), + 'frac_digits' => $defaultCurrency->decimal_places, ]; View::share('monthFormat', $monthFormat); View::share('monthAndDayFormat', $monthAndDayFormat); View::share('dateTimeFormat', $dateTimeFormat); View::share('language', $lang); View::share('localeconv', $localeconv); + View::share('defaultCurrency', $defaultCurrency); } /** diff --git a/app/Http/Requests/AccountFormRequest.php b/app/Http/Requests/AccountFormRequest.php index 1e219707ee..364898614c 100644 --- a/app/Http/Requests/AccountFormRequest.php +++ b/app/Http/Requests/AccountFormRequest.php @@ -43,13 +43,13 @@ class AccountFormRequest extends Request 'active' => intval($this->input('active')) === 1, 'accountType' => $this->input('what'), 'currency_id' => intval($this->input('currency_id')), - 'virtualBalance' => round($this->input('virtualBalance'), 2), + 'virtualBalance' => round($this->input('virtualBalance'), 12), 'virtualBalanceCurrency' => intval($this->input('amount_currency_id_virtualBalance')), 'iban' => trim(strval($this->input('iban'))), 'BIC' => trim(strval($this->input('BIC'))), 'accountNumber' => trim(strval($this->input('accountNumber'))), 'accountRole' => $this->input('accountRole'), - 'openingBalance' => round($this->input('openingBalance'), 2), + 'openingBalance' => round($this->input('openingBalance'), 12), 'openingBalanceDate' => new Carbon((string)$this->input('openingBalanceDate')), 'openingBalanceCurrency' => intval($this->input('amount_currency_id_openingBalance')), 'ccType' => $this->input('ccType'), diff --git a/app/Http/Requests/BillFormRequest.php b/app/Http/Requests/BillFormRequest.php index b16ce0ec33..c27a24377d 100644 --- a/app/Http/Requests/BillFormRequest.php +++ b/app/Http/Requests/BillFormRequest.php @@ -40,10 +40,10 @@ class BillFormRequest extends Request return [ 'name' => $this->get('name'), 'match' => $this->get('match'), - 'amount_min' => round($this->get('amount_min'), 2), + 'amount_min' => round($this->get('amount_min'), 12), 'amount_currency_id_amount_min' => intval($this->get('amount_currency_id_amount_min')), 'amount_currency_id_amount_max' => intval($this->get('amount_currency_id_amount_max')), - 'amount_max' => round($this->get('amount_max'), 2), + 'amount_max' => round($this->get('amount_max'), 12), 'date' => new Carbon($this->get('date')), 'repeat_freq' => $this->get('repeat_freq'), 'skip' => intval($this->get('skip')), diff --git a/app/Http/Requests/ConfigurationRequest.php b/app/Http/Requests/ConfigurationRequest.php index 1647835a3b..d6bd5d7ca2 100644 --- a/app/Http/Requests/ConfigurationRequest.php +++ b/app/Http/Requests/ConfigurationRequest.php @@ -36,14 +36,8 @@ class ConfigurationRequest extends Request public function getConfigurationData(): array { return [ - 'single_user_mode' => intval($this->get('single_user_mode')) === 1, - 'must_confirm_account' => intval($this->get('must_confirm_account')) === 1, - 'is_demo_site' => intval($this->get('is_demo_site')) === 1, - 'mail_for_lockout' => intval($this->get('mail_for_lockout')) === 1, - 'mail_for_blocked_domain' => intval($this->get('mail_for_blocked_domain')) === 1, - 'mail_for_blocked_email' => intval($this->get('mail_for_blocked_email')) === 1, - 'mail_for_bad_login' => intval($this->get('mail_for_bad_login')) === 1, - 'mail_for_blocked_login' => intval($this->get('mail_for_blocked_login')) === 1, + 'single_user_mode' => intval($this->get('single_user_mode')) === 1, + 'is_demo_site' => intval($this->get('is_demo_site')) === 1, ]; } @@ -53,14 +47,8 @@ class ConfigurationRequest extends Request public function rules() { $rules = [ - 'single_user_mode' => 'between:0,1|numeric', - 'must_confirm_account' => 'between:0,1|numeric', - 'is_demo_site' => 'between:0,1|numeric', - 'mail_for_lockout' => 'between:0,1|numeric', - 'mail_for_blocked_domain' => 'between:0,1|numeric', - 'mail_for_blocked_email' => 'between:0,1|numeric', - 'mail_for_bad_login' => 'between:0,1|numeric', - 'mail_for_blocked_login' => 'between:0,1|numeric', + 'single_user_mode' => 'between:0,1|numeric', + 'is_demo_site' => 'between:0,1|numeric', ]; return $rules; diff --git a/app/Http/Requests/CurrencyFormRequest.php b/app/Http/Requests/CurrencyFormRequest.php index 74b805be84..ffbfa7e399 100644 --- a/app/Http/Requests/CurrencyFormRequest.php +++ b/app/Http/Requests/CurrencyFormRequest.php @@ -36,9 +36,10 @@ class CurrencyFormRequest extends Request public function getCurrencyData() { return [ - 'name' => $this->get('name'), - 'code' => $this->get('code'), - 'symbol' => $this->get('symbol'), + 'name' => $this->get('name'), + 'code' => $this->get('code'), + 'symbol' => $this->get('symbol'), + 'decimal_places' => intval($this->get('decimal_places')), ]; } @@ -49,15 +50,17 @@ class CurrencyFormRequest extends Request { $rules = [ - 'code' => 'required|min:3|max:3|unique:transaction_currencies,code', - 'name' => 'required|max:48|min:1|unique:transaction_currencies,name', - 'symbol' => 'required|min:1|max:8|unique:transaction_currencies,symbol', + 'name' => 'required|max:48|min:1|unique:transaction_currencies,name', + 'code' => 'required|min:3|max:3|unique:transaction_currencies,code', + 'symbol' => 'required|min:1|max:8|unique:transaction_currencies,symbol', + 'decimal_places' => 'required|min:0|max:12|numeric', ]; if (intval($this->get('id')) > 0) { $rules = [ - 'code' => 'required|min:3|max:3', - 'name' => 'required|max:48|min:1', - 'symbol' => 'required|min:1|max:8', + 'name' => 'required|max:48|min:1', + 'code' => 'required|min:3|max:3', + 'symbol' => 'required|min:1|max:8', + 'decimal_places' => 'required|min:0|max:12|numeric', ]; } diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index 7f582042a7..69734b26bf 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -59,7 +59,7 @@ class JournalFormRequest extends Request // transaction / journal data: 'description' => $this->getFieldOrEmptyString('description'), - 'amount' => round($this->get('amount'), 2), + 'amount' => round($this->get('amount'), 12), 'budget_id' => intval($this->get('budget_id')), 'category' => $this->getFieldOrEmptyString('category'), 'source_account_id' => intval($this->get('source_account_id')), diff --git a/app/Http/Requests/PiggyBankFormRequest.php b/app/Http/Requests/PiggyBankFormRequest.php index 684ad148d9..3411975166 100644 --- a/app/Http/Requests/PiggyBankFormRequest.php +++ b/app/Http/Requests/PiggyBankFormRequest.php @@ -41,7 +41,7 @@ class PiggyBankFormRequest extends Request 'name' => trim($this->get('name')), 'startdate' => new Carbon, 'account_id' => intval($this->get('account_id')), - 'targetamount' => round($this->get('targetamount'), 2), + 'targetamount' => round($this->get('targetamount'), 12), 'targetdate' => strlen(strval($this->get('targetdate'))) > 0 ? new Carbon($this->get('targetdate')) : null, 'note' => trim(strval($this->get('note'))), ]; diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php index 539838c794..2bf7100677 100644 --- a/app/Http/Requests/SplitJournalFormRequest.php +++ b/app/Http/Requests/SplitJournalFormRequest.php @@ -93,7 +93,7 @@ class SplitJournalFormRequest extends Request $category = $this->get('category')[$index] ?? ''; $transaction = [ 'description' => $description, - 'amount' => round($this->get('amount')[$index], 2), + 'amount' => round($this->get('amount')[$index], 12), 'budget_id' => $this->get('budget_id')[$index] ? intval($this->get('budget_id')[$index]) : 0, 'category' => trim($category), 'source_account_id' => isset($this->get('source_account_id')[$index]) diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index 339855c695..b7a64a5c07 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -17,9 +17,9 @@ use FireflyIII\Models\Account; use FireflyIII\Models\Attachment; use FireflyIII\Models\Bill; use FireflyIII\Models\Budget; +use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\Category; use FireflyIII\Models\ImportJob; -use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; @@ -274,11 +274,20 @@ Breadcrumbs::register( ); Breadcrumbs::register( - 'budgets.show.repetition', function (BreadCrumbGenerator $breadcrumbs, Budget $budget, LimitRepetition $repetition) { + 'budgets.show.limit', function (BreadCrumbGenerator $breadcrumbs, Budget $budget, BudgetLimit $budgetLimit) { $breadcrumbs->parent('budgets.index'); - $breadcrumbs->push(e($budget->name), route('budgets.show.repetition', [$budget->id, $repetition->id])); + $breadcrumbs->push(e($budget->name), route('budgets.show', [$budget->id])); + + $title = trans( + 'firefly.budget_in_period_breadcrumb', [ + 'name' => $budget->name, + 'start' => $budgetLimit->start_date->formatLocalized(strval(trans('config.month_and_day'))), + 'end' => $budgetLimit->end_date->formatLocalized(strval(trans('config.month_and_day'))), + ] + ); + $breadcrumbs->push( - Navigation::periodShow($repetition->startdate, $repetition->budgetLimit->repeat_freq), route('budgets.show', [$budget->id, $repetition->id]) + $title, route('budgets.show.limit', [$budget->id, $budgetLimit->id]) ); } ); @@ -320,6 +329,14 @@ Breadcrumbs::register( } ); +Breadcrumbs::register( + 'categories.show.all', function (BreadCrumbGenerator $breadcrumbs, Category $category) { + $breadcrumbs->parent('categories.index'); + $breadcrumbs->push(e($category->name) . '(' . strtolower(trans('firefly.all_periods')) . ')', route('categories.show.all', [$category->id])); + +} +); + Breadcrumbs::register( 'categories.show.date', function (BreadCrumbGenerator $breadcrumbs, Category $category, Carbon $date) { diff --git a/app/Import/Converter/Amount.php b/app/Import/Converter/Amount.php index 6b64a2b862..ab0fceb6b7 100644 --- a/app/Import/Converter/Amount.php +++ b/app/Import/Converter/Amount.php @@ -62,7 +62,7 @@ class Amount extends BasicConverter implements ConverterInterface $this->setCertainty(90); - return round(floatval($value), 4); + return round(floatval($value), 12); } } diff --git a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php index fec910a6b3..1678cb2bcc 100644 --- a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php +++ b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php @@ -14,7 +14,7 @@ declare(strict_types = 1); namespace FireflyIII\Jobs; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\RuleGroup; use FireflyIII\Rules\Processor; use FireflyIII\User; @@ -155,7 +155,8 @@ class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue */ protected function collectJournals() { - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAccounts($this->accounts)->setRange($this->startDate, $this->endDate); return $collector->getJournals(); diff --git a/app/Models/Account.php b/app/Models/Account.php index 2d07b89f37..4810c6006e 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -69,10 +69,14 @@ class Account extends Model /** * @param array $fields * - * @return Account|null + * @return Account + * @throws FireflyException */ public static function firstOrCreateEncrypted(array $fields) { + if (!isset($fields['user_id'])) { + throw new FireflyException('Missing required field "user_id".'); + } // everything but the name: $query = self::orderBy('id'); $search = $fields; @@ -81,17 +85,21 @@ class Account extends Model foreach ($search as $name => $value) { $query->where($name, $value); } - $set = $query->get(['accounts.*']); + $set = $query->get(['accounts.*']); + + // account must have a name. If not set, use IBAN. + if (!isset($fields['name'])) { + $fields['name'] = $fields['iban']; + } + + + /** @var Account $account */ foreach ($set as $account) { if ($account->name == $fields['name']) { return $account; } } - // account must have a name. If not set, use IBAN. - if (!isset($fields['name'])) { - $fields['name'] = $fields['iban']; - } // create it! $account = self::create($fields); @@ -318,7 +326,7 @@ class Account extends Model */ public function setVirtualBalanceAttribute($value) { - $this->attributes['virtual_balance'] = strval(round($value, 2)); + $this->attributes['virtual_balance'] = strval(round($value, 12)); } /** diff --git a/app/Models/AccountMeta.php b/app/Models/AccountMeta.php index 797058c122..48953f09d5 100644 --- a/app/Models/AccountMeta.php +++ b/app/Models/AccountMeta.php @@ -35,7 +35,7 @@ class AccountMeta extends Model 'updated_at' => 'date', ]; /** @var array */ - protected $dates = ['created_at', 'updated_at']; + protected $dates = ['created_at', 'updated_at']; protected $fillable = ['account_id', 'name', 'data']; protected $table = 'account_meta'; diff --git a/app/Models/Attachment.php b/app/Models/Attachment.php index 84dfbfc106..36599f3d6b 100644 --- a/app/Models/Attachment.php +++ b/app/Models/Attachment.php @@ -43,7 +43,7 @@ class Attachment extends Model ]; /** @var array */ protected $dates = ['created_at', 'updated_at', 'deleted_at']; - /** @var array */ + /** @var array */ protected $fillable = ['attachable_id', 'attachable_type', 'user_id', 'md5', 'filename', 'mime', 'title', 'notes', 'description', 'size', 'uploaded']; /** diff --git a/app/Models/AvailableBudget.php b/app/Models/AvailableBudget.php index c96893fbb9..d51d8b0418 100644 --- a/app/Models/AvailableBudget.php +++ b/app/Models/AvailableBudget.php @@ -26,11 +26,6 @@ use Illuminate\Database\Eloquent\SoftDeletes; class AvailableBudget extends Model { use SoftDeletes; - /** @var array */ - protected $fillable = ['user_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date']; - - /** @var array */ - protected $dates = ['created_at', 'updated_at', 'deleted_at']; /** * The attributes that should be casted to native types. * @@ -42,8 +37,12 @@ class AvailableBudget extends Model 'updated_at' => 'date', 'deleted_at' => 'date', 'start_date' => 'date', - 'end_date' => 'date', + 'end_date' => 'date', ]; + /** @var array */ + protected $dates = ['created_at', 'updated_at', 'deleted_at']; + /** @var array */ + protected $fillable = ['user_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date']; /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo diff --git a/app/Models/Bill.php b/app/Models/Bill.php index a536caa18e..bf67e0f730 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -29,9 +29,6 @@ class Bill extends Model { use ValidatingTrait; - /** @var array */ - protected $dates = ['created_at', 'updated_at', 'deleted_at']; - /** * The attributes that should be casted to native types. * @@ -49,6 +46,8 @@ class Bill extends Model 'name_encrypted' => 'boolean', 'match_encrypted' => 'boolean', ]; + /** @var array */ + protected $dates = ['created_at', 'updated_at', 'deleted_at']; protected $fillable = ['name', 'match', 'amount_min', 'match_encrypted', 'name_encrypted', 'user_id', 'amount_max', 'date', 'repeat_freq', 'skip', 'automatch', 'active',]; @@ -105,7 +104,7 @@ class Bill extends Model */ public function setAmountMaxAttribute($value) { - $this->attributes['amount_max'] = strval(round($value, 2)); + $this->attributes['amount_max'] = strval(round($value, 12)); } /** @@ -113,7 +112,7 @@ class Bill extends Model */ public function setAmountMinAttribute($value) { - $this->attributes['amount_min'] = strval(round($value, 2)); + $this->attributes['amount_min'] = strval(round($value, 12)); } /** diff --git a/app/Models/Budget.php b/app/Models/Budget.php index 6bca976677..d62e195352 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -30,9 +30,6 @@ class Budget extends Model use SoftDeletes, ValidatingTrait; - /** @var array */ - protected $dates = ['created_at', 'updated_at', 'deleted_at']; - /** * The attributes that should be casted to native types. * @@ -46,7 +43,8 @@ class Budget extends Model 'active' => 'boolean', 'encrypted' => 'boolean', ]; - + /** @var array */ + protected $dates = ['created_at', 'updated_at', 'deleted_at']; protected $fillable = ['user_id', 'name', 'active']; protected $hidden = ['encrypted']; protected $rules = ['name' => 'required|between:1,200',]; @@ -118,14 +116,6 @@ class Budget extends Model return $value; } - /** - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough - */ - public function limitrepetitions() - { - return $this->hasManyThrough('FireflyIII\Models\LimitRepetition', 'FireflyIII\Models\BudgetLimit', 'budget_id'); - } - /** * @param $value */ diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php index 622ad9bbc7..a6c2b68f97 100644 --- a/app/Models/BudgetLimit.php +++ b/app/Models/BudgetLimit.php @@ -14,6 +14,7 @@ declare(strict_types = 1); namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class BudgetLimit @@ -29,15 +30,34 @@ class BudgetLimit extends Model * @var array */ protected $casts - = [ + = [ 'created_at' => 'date', 'updated_at' => 'date', - 'startdate' => 'date', + 'start_date' => 'date', + 'end_date' => 'date', 'repeats' => 'boolean', ]; /** @var array */ - protected $dates = ['created_at', 'updated_at']; - protected $hidden = ['amount_encrypted']; + protected $dates = ['created_at', 'updated_at', 'start_date', 'end_date']; + + /** + * @param $value + * + * @return mixed + */ + public static function routeBinder($value) + { + if (auth()->check()) { + $object = self::where('budget_limits.id', $value) + ->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') + ->where('budgets.user_id', auth()->user()->id) + ->first(['budget_limits.*']); + if ($object) { + return $object; + } + } + throw new NotFoundHttpException; + } /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo @@ -55,12 +75,13 @@ class BudgetLimit extends Model return $this->hasMany('FireflyIII\Models\LimitRepetition'); } + /** * @param $value */ public function setAmountAttribute($value) { - $this->attributes['amount'] = strval(round($value, 2)); + $this->attributes['amount'] = strval(round($value, 12)); } } diff --git a/app/Models/Category.php b/app/Models/Category.php index c0db1259b8..e8e665bff2 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -41,14 +41,14 @@ class Category extends Model 'deleted_at' => 'date', 'encrypted' => 'boolean', ]; - /** @var array */ - protected $fillable = ['user_id', 'name']; - /** @var array */ - protected $hidden = ['encrypted']; - /** @var array */ - protected $rules = ['name' => 'required|between:1,200',]; /** @var array */ protected $dates = ['created_at', 'updated_at', 'deleted_at']; + /** @var array */ + protected $fillable = ['user_id', 'name']; + /** @var array */ + protected $hidden = ['encrypted']; + /** @var array */ + protected $rules = ['name' => 'required|between:1,200',]; /** * @param array $fields diff --git a/app/Models/LimitRepetition.php b/app/Models/LimitRepetition.php index 9c76eab93f..d4f5b5f3dc 100644 --- a/app/Models/LimitRepetition.php +++ b/app/Models/LimitRepetition.php @@ -21,6 +21,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class LimitRepetition * + * @deprecated * @package FireflyIII\Models */ class LimitRepetition extends Model @@ -32,7 +33,7 @@ class LimitRepetition extends Model * @var array */ protected $casts - = [ + = [ 'created_at' => 'date', 'updated_at' => 'date', 'startdate' => 'date', diff --git a/app/Models/Note.php b/app/Models/Note.php index 58600a8073..55ee792ce9 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -29,7 +29,7 @@ class Note extends Model * @var array */ protected $casts - = [ + = [ 'created_at' => 'date', 'updated_at' => 'date', 'deleted_at' => 'date', diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index e9b6b4a980..34c742bd7f 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -35,7 +35,7 @@ class PiggyBank extends Model * @var array */ protected $casts - = [ + = [ 'created_at' => 'date', 'updated_at' => 'date', 'deleted_at' => 'date', @@ -168,6 +168,6 @@ class PiggyBank extends Model */ public function setTargetamountAttribute($value) { - $this->attributes['targetamount'] = strval(round($value, 2)); + $this->attributes['targetamount'] = strval(round($value, 12)); } } diff --git a/app/Models/PiggyBankEvent.php b/app/Models/PiggyBankEvent.php index be731402ab..a84383100d 100644 --- a/app/Models/PiggyBankEvent.php +++ b/app/Models/PiggyBankEvent.php @@ -29,7 +29,7 @@ class PiggyBankEvent extends Model * @var array */ protected $casts - = [ + = [ 'created_at' => 'date', 'updated_at' => 'date', 'date' => 'date', diff --git a/app/Models/PiggyBankRepetition.php b/app/Models/PiggyBankRepetition.php index 8b048879df..1d9dca1eb0 100644 --- a/app/Models/PiggyBankRepetition.php +++ b/app/Models/PiggyBankRepetition.php @@ -31,7 +31,7 @@ class PiggyBankRepetition extends Model * @var array */ protected $casts - = [ + = [ 'created_at' => 'date', 'updated_at' => 'date', 'deleted_at' => 'date', diff --git a/app/Models/Preference.php b/app/Models/Preference.php index 5ec7b943ad..f5a81dd909 100644 --- a/app/Models/Preference.php +++ b/app/Models/Preference.php @@ -33,7 +33,7 @@ class Preference extends Model * @var array */ protected $casts - = [ + = [ 'created_at' => 'date', 'updated_at' => 'date', ]; diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php index b4be42a160..98cbdc0889 100644 --- a/app/Models/RuleGroup.php +++ b/app/Models/RuleGroup.php @@ -32,11 +32,11 @@ class RuleGroup extends Model */ protected $casts = [ - 'created_at' => 'date', - 'updated_at' => 'date', - 'deleted_at' => 'date', - 'active' => 'boolean', - 'order' => 'int', + 'created_at' => 'date', + 'updated_at' => 'date', + 'deleted_at' => 'date', + 'active' => 'boolean', + 'order' => 'int', ]; /** @var array */ protected $dates = ['created_at', 'updated_at', 'deleted_at']; diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 5b5036d7a0..70061c8072 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -157,7 +157,7 @@ class Transaction extends Model */ public function setAmountAttribute($value) { - $this->attributes['amount'] = strval(round($value, 2)); + $this->attributes['amount'] = strval(round($value, 12)); } /** diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index aded707d80..0a63b24329 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -27,20 +27,28 @@ class TransactionCurrency extends Model { use SoftDeletes, ValidatingTrait; - protected $dates = ['created_at', 'updated_at', 'deleted_at','date']; - protected $fillable = ['name', 'code', 'symbol']; - protected $rules = ['name' => 'required|between:1,200', 'code' => 'required|between:3,3', 'symbol' => 'required|between:1,12']; /** * The attributes that should be casted to native types. * * @var array */ protected $casts - = [ - 'created_at' => 'date', - 'updated_at' => 'date', - 'deleted_at' => 'date', + = [ + 'created_at' => 'date', + 'updated_at' => 'date', + 'deleted_at' => 'date', + 'decimal_places' => 'int', ]; + protected $dates = ['created_at', 'updated_at', 'deleted_at', 'date']; + protected $fillable = ['name', 'code', 'symbol', 'decimal_places']; + protected $rules + = [ + 'name' => 'required|between:1,48', + 'code' => 'required|between:3,3', + 'symbol' => 'required|between:1,8', + 'decimal_places' => 'required|min:0|max:12|numeric', + ]; + /** * @param TransactionCurrency $currency * diff --git a/app/Models/TransactionJournalMeta.php b/app/Models/TransactionJournalMeta.php index 6d54430a47..a3f6ec92ba 100644 --- a/app/Models/TransactionJournalMeta.php +++ b/app/Models/TransactionJournalMeta.php @@ -32,7 +32,7 @@ class TransactionJournalMeta extends Model * @var array */ protected $casts - = [ + = [ 'created_at' => 'date', 'updated_at' => 'date', 'deleted_at' => 'date', diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index e9c971f2b2..215fc5978e 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -37,62 +37,14 @@ class EventServiceProvider extends ServiceProvider protected $listen = [ // new event handlers: - 'FireflyIII\Events\ConfirmedUser' => // is a User related event. - [ - 'FireflyIII\Handlers\Events\UserEventHandler@storeConfirmationIpAddress', - ], - - 'FireflyIII\Events\DeletedUser' => // is a User related event. - [ - 'FireflyIII\Handlers\Events\UserEventHandler@saveEmailAddress', - ], - 'FireflyIII\Events\LockedOutUser' => // is a User related event. - [ - 'FireflyIII\Handlers\Events\UserEventHandler@reportLockout', - ], - 'FireflyIII\Events\BlockedUserLogin' => // is a User related event. - [ - 'FireflyIII\Handlers\Events\UserEventHandler@reportBlockedUser', - ], - - 'FireflyIII\Events\BlockedUseOfEmail' => // is a User related event. - [ - 'FireflyIII\Handlers\Events\UserEventHandler@reportUseOfBlockedEmail', - ], - - 'FireflyIII\Events\BlockedUseOfDomain' => // is a User related event. - [ - 'FireflyIII\Handlers\Events\UserEventHandler@reportUseBlockedDomain', - ], - - 'FireflyIII\Events\BlockedBadLogin' => // is a User related event. - [ - 'FireflyIII\Handlers\Events\UserEventHandler@reportBadLogin', - ], - 'FireflyIII\Events\RegisteredUser' => // is a User related event. + 'FireflyIII\Events\RegisteredUser' => // is a User related event. [ 'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationMail', 'FireflyIII\Handlers\Events\UserEventHandler@attachUserRole', - 'FireflyIII\Handlers\Events\UserEventHandler@sendConfirmationMessage', - 'FireflyIII\Handlers\Events\UserEventHandler@storeRegistrationIpAddress', ], - 'FireflyIII\Events\RequestedNewPassword' => [ // is a User related event. - 'FireflyIII\Handlers\Events\UserEventHandler@sendNewPassword', + 'FireflyIII\Events\RequestedNewPassword' => [ // is a User related event. + 'FireflyIII\Handlers\Events\UserEventHandler@sendNewPassword', ], - 'FireflyIII\Events\ResentConfirmation' => // is a User related event. - [ - 'FireflyIII\Handlers\Events\UserEventHandler@sendConfirmationMessageAgain', - ], - 'FireflyIII\Events\StoredBudgetLimit' => // is a Budget related event. - [ - 'FireflyIII\Handlers\Events\BudgetEventHandler@storeRepetition', - ], - - 'FireflyIII\Events\UpdatedBudgetLimit' => // is a Budget related event. - [ - 'FireflyIII\Handlers\Events\BudgetEventHandler@updateRepetition', - ], - 'FireflyIII\Events\StoredTransactionJournal' => // is a Transaction Journal related event. [ 'FireflyIII\Handlers\Events\StoredJournalEventHandler@scanBills', diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index 2a49d5b15e..b798f531e0 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -96,6 +96,9 @@ class FireflyServiceProvider extends ServiceProvider // chart generator: $this->app->bind('FireflyIII\Generator\Chart\Basic\GeneratorInterface', 'FireflyIII\Generator\Chart\Basic\ChartJsGenerator'); + // chart builder + $this->app->bind('FireflyIII\Helpers\Chart\MetaPieChartInterface', 'FireflyIII\Helpers\Chart\MetaPieChart'); + // other generators $this->app->bind('FireflyIII\Export\ProcessorInterface', 'FireflyIII\Export\Processor'); $this->app->bind('FireflyIII\Import\ImportProcedureInterface', 'FireflyIII\Import\ImportProcedure'); diff --git a/app/Repositories/Attachment/AttachmentRepositoryInterface.php b/app/Repositories/Attachment/AttachmentRepositoryInterface.php index 733e3bd59f..9e12092c70 100644 --- a/app/Repositories/Attachment/AttachmentRepositoryInterface.php +++ b/app/Repositories/Attachment/AttachmentRepositoryInterface.php @@ -32,11 +32,6 @@ interface AttachmentRepositoryInterface */ public function destroy(Attachment $attachment): bool; - /** - * @return Collection - */ - public function get(): Collection; - /** * @param Attachment $attachment * @@ -45,11 +40,9 @@ interface AttachmentRepositoryInterface public function exists(Attachment $attachment): bool; /** - * @param Attachment $attachment - * - * @return string + * @return Collection */ - public function getContent(Attachment $attachment): string; + public function get(): Collection; /** * @param Carbon $start @@ -59,6 +52,13 @@ interface AttachmentRepositoryInterface */ public function getBetween(Carbon $start, Carbon $end): Collection; + /** + * @param Attachment $attachment + * + * @return string + */ + public function getContent(Attachment $attachment): string; + /** * @param Attachment $attachment * @param array $attachmentData diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 92645343aa..d272982654 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -14,13 +14,10 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Budget; use Carbon\Carbon; -use FireflyIII\Events\StoredBudgetLimit; -use FireflyIII\Events\UpdatedBudgetLimit; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\AvailableBudget; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; -use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; @@ -199,30 +196,38 @@ class BudgetRepository implements BudgetRepositoryInterface * * @return Collection */ - public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection + public function getAllBudgetLimits(Carbon $start, Carbon $end): Collection { - $query = LimitRepetition::leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') - ->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') - ->where( - function (Builder $q1) use ($start, $end) { - $q1->where( - function (Builder $q2) use ($start, $end) { - $q2->where('limit_repetitions.enddate', '>=', $start->format('Y-m-d 00:00:00')); - $q2->where('limit_repetitions.enddate', '<=', $end->format('Y-m-d 00:00:00')); - } - ) - ->orWhere( - function (Builder $q3) use ($start, $end) { - $q3->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00')); - $q3->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')); - } - ); - } - ) - ->where('budgets.user_id', $this->user->id) - ->whereNull('budgets.deleted_at'); - - $set = $query->get(['limit_repetitions.*', 'budget_limits.budget_id']); + $set = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') + ->with(['budget']) + ->where('budgets.user_id', $this->user->id) + ->where( + function (Builder $q5) use ($start, $end) { + $q5->where( + function (Builder $q1) use ($start, $end) { + $q1->where( + function (Builder $q2) use ($start, $end) { + $q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00')); + $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 00:00:00')); + } + ) + ->orWhere( + function (Builder $q3) use ($start, $end) { + $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00')); + $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 00:00:00')); + } + ); + } + ) + ->orWhere( + function (Builder $q4) use ($start, $end) { + // or start is before start AND end is after end. + $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 00:00:00')); + $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00')); + } + ); + } + )->get(['budget_limits.*']); return $set; } @@ -248,6 +253,49 @@ class BudgetRepository implements BudgetRepositoryInterface return $amount; } + /** + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getBudgetLimits(Budget $budget, Carbon $start, Carbon $end): Collection + { + $set = $budget->budgetLimits() + ->where( + function (Builder $q5) use ($start, $end) { + $q5->where( + function (Builder $q1) use ($start, $end) { + // budget limit ends within period + $q1->where( + function (Builder $q2) use ($start, $end) { + $q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00')); + $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 00:00:00')); + } + ) + // budget limit start within period + ->orWhere( + function (Builder $q3) use ($start, $end) { + $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00')); + $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 00:00:00')); + } + ); + } + ) + ->orWhere( + function (Builder $q4) use ($start, $end) { + // or start is before start AND end is after end. + $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 00:00:00')); + $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00')); + } + ); + } + )->orderBy('budget_limits.start_date','DESC')->get(['budget_limits.*']); + + return $set; + } + /** * This method is being used to generate the budget overview in the year/multi-year report. Its used * in both the year/multi-year budget overview AND in the accompanying chart. @@ -578,19 +626,18 @@ class BudgetRepository implements BudgetRepositoryInterface * @param Budget $budget * @param Carbon $start * @param Carbon $end - * @param string $range * @param int $amount * * @return BudgetLimit */ - public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $range, int $amount): BudgetLimit + public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, int $amount): BudgetLimit { - // there might be a budget limit for this startdate: - $repeatFreq = config('firefly.range_to_repeat_freq.' . $range); + // there might be a budget limit for these dates: /** @var BudgetLimit $limit */ $limit = $budget->budgetlimits() - ->where('budget_limits.startdate', $start) - ->where('budget_limits.repeat_freq', $repeatFreq)->first(['budget_limits.*']); + ->where('budget_limits.start_date', $start->format('Y-m-d')) + ->where('budget_limits.end_date', $end->format('Y-m-d')) + ->first(['budget_limits.*']); // delete if amount is zero. if (!is_null($limit) && $amount <= 0.0) { @@ -603,26 +650,16 @@ class BudgetRepository implements BudgetRepositoryInterface $limit->amount = $amount; $limit->save(); - // fire event to create or update LimitRepetition. - event(new UpdatedBudgetLimit($limit, $end)); - return $limit; } - // create one and return it. + // or create one and return it. $limit = new BudgetLimit; $limit->budget()->associate($budget); - $limit->startdate = $start; - $limit->amount = $amount; - $limit->repeat_freq = $repeatFreq; - $limit->repeats = 0; + $limit->start_date = $start; + $limit->end_date = $end; + $limit->amount = $amount; $limit->save(); - event(new StoredBudgetLimit($limit, $end)); - - - // likewise, there should be a limit repetition to match the end date - // (which is always the end of the month) but that is caught by an event. - // so handled automatically. return $limit; } diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 8d04c7bff6..6ccb7da0bf 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -89,7 +89,7 @@ interface BudgetRepositoryInterface * * @return Collection */ - public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection; + public function getAllBudgetLimits(Carbon $start, Carbon $end): Collection; /** * @param TransactionCurrency $currency @@ -100,6 +100,15 @@ interface BudgetRepositoryInterface */ public function getAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end): string; + /** + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getBudgetLimits(Budget $budget, Carbon $start, Carbon $end): Collection; + /** * * @param Collection $budgets @@ -178,11 +187,10 @@ interface BudgetRepositoryInterface * @param Budget $budget * @param Carbon $start * @param Carbon $end - * @param string $range * @param int $amount * * @return BudgetLimit */ - public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $range, int $amount): BudgetLimit; + public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, int $amount): BudgetLimit; } diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 65b46ad44c..9b881df691 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -134,27 +134,28 @@ class CategoryRepository implements CategoryRepositoryInterface { $first = null; - - /** @var TransactionJournal $first */ + /** @var TransactionJournal $firstJournal */ $firstJournal = $category->transactionJournals()->orderBy('date', 'ASC')->first(['transaction_journals.date']); if ($firstJournal) { $first = $firstJournal->date; } - // check transactions: - $firstTransaction = $category->transactions() ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->orderBy('transaction_journals.date', 'ASC')->first(['transaction_journals.date']); - if (!is_null($firstTransaction) && ((!is_null($first) && $firstTransaction->date < $first) || is_null($first))) { - $first = new Carbon($firstTransaction->date); + + // both exist, the one that is earliest "wins". + if (!is_null($firstTransaction) && !is_null($first) && Carbon::parse($firstTransaction->date)->lt($first)) { + $first = $firstTransaction->date; } + if (is_null($first)) { return new Carbon('1900-01-01'); } + return $first; } diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php index 40aeea18d7..d2eaed1c30 100644 --- a/app/Repositories/Currency/CurrencyRepository.php +++ b/app/Repositories/Currency/CurrencyRepository.php @@ -197,9 +197,10 @@ class CurrencyRepository implements CurrencyRepositoryInterface { $currency = TransactionCurrency::create( [ - 'name' => $data['name'], - 'code' => $data['code'], - 'symbol' => $data['symbol'], + 'name' => $data['name'], + 'code' => $data['code'], + 'symbol' => $data['symbol'], + 'decimal_places' => $data['decimal_places'], ] ); @@ -214,9 +215,10 @@ class CurrencyRepository implements CurrencyRepositoryInterface */ public function update(TransactionCurrency $currency, array $data): TransactionCurrency { - $currency->code = $data['code']; - $currency->symbol = $data['symbol']; - $currency->name = $data['name']; + $currency->code = $data['code']; + $currency->symbol = $data['symbol']; + $currency->name = $data['name']; + $currency->decimal_places = $data['decimal_places']; $currency->save(); return $currency; diff --git a/app/Repositories/ExportJob/ExportJobRepository.php b/app/Repositories/ExportJob/ExportJobRepository.php index acb8999786..b83818284a 100644 --- a/app/Repositories/ExportJob/ExportJobRepository.php +++ b/app/Repositories/ExportJob/ExportJobRepository.php @@ -39,6 +39,19 @@ class ExportJobRepository implements ExportJobRepositoryInterface $this->user = $user; } + /** + * @param ExportJob $job + * @param string $status + * + * @return bool + */ + public function changeStatus(ExportJob $job, string $status): bool + { + $job->change($status); + + return true; + } + /** * @return bool */ @@ -111,7 +124,7 @@ class ExportJobRepository implements ExportJobRepositoryInterface /** * @param string $key * - * @return ExportJob|null + * @return ExportJob */ public function findByKey(string $key): ExportJob { diff --git a/app/Repositories/ExportJob/ExportJobRepositoryInterface.php b/app/Repositories/ExportJob/ExportJobRepositoryInterface.php index 650aac15a6..dbb4a04418 100644 --- a/app/Repositories/ExportJob/ExportJobRepositoryInterface.php +++ b/app/Repositories/ExportJob/ExportJobRepositoryInterface.php @@ -22,6 +22,14 @@ use FireflyIII\Models\ExportJob; */ interface ExportJobRepositoryInterface { + /** + * @param ExportJob $job + * @param string $status + * + * @return bool + */ + public function changeStatus(ExportJob $job, string $status): bool; + /** * @return bool */ diff --git a/app/Repositories/User/UserRepository.php b/app/Repositories/User/UserRepository.php index 6e38137f9c..ec2a6c7442 100644 --- a/app/Repositories/User/UserRepository.php +++ b/app/Repositories/User/UserRepository.php @@ -14,8 +14,6 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\User; -use FireflyConfig; -use FireflyIII\Events\DeletedUser; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\Role; use FireflyIII\User; @@ -54,6 +52,20 @@ class UserRepository implements UserRepositoryInterface return true; } + /** + * @param User $user + * @param string $password + * + * @return bool + */ + public function changePassword(User $user, string $password): bool + { + $user->password = bcrypt($password); + $user->save(); + + return true; + } + /** * @return int */ @@ -69,14 +81,9 @@ class UserRepository implements UserRepositoryInterface */ public function destroy(User $user): bool { - $email = $user->email; Log::debug(sprintf('Calling delete() on user %d', $user->id)); $user->delete(); - - // trigger event: - event(new DeletedUser($email)); - return true; } @@ -114,14 +121,6 @@ class UserRepository implements UserRepositoryInterface $return['has_2fa'] = true; } - // is user activated? - $mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data; - $isConfirmed = Preferences::getForUser($user, 'user_confirmed', false)->data; - $return['is_activated'] = true; - if ($isConfirmed === false && $mustConfirmAccount === true) { - $return['is_activated'] = false; - } - $return['is_admin'] = $user->hasRole('owner'); $return['blocked'] = intval($user->blocked) === 1; $return['blocked_code'] = $user->blocked_code; @@ -148,17 +147,4 @@ class UserRepository implements UserRepositoryInterface return $return; } - - /** - * @param User $user - * @param string $password - * - * @return bool - */ - public function changePassword(User $user, string $password): bool - { - $user->password = bcrypt($password); - $user->save(); - return true; - } } diff --git a/app/Rules/TransactionMatcher.php b/app/Rules/TransactionMatcher.php index 0e63c06085..acac7c8521 100644 --- a/app/Rules/TransactionMatcher.php +++ b/app/Rules/TransactionMatcher.php @@ -13,7 +13,7 @@ declare(strict_types = 1); namespace FireflyIII\Rules; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Journal\JournalTaskerInterface; @@ -77,7 +77,8 @@ class TransactionMatcher // - the maximum number of transactions to search in have been searched do { // Fetch a batch of transactions from the database - $collector = new JournalCollector(auth()->user()); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [auth()->user()]); $collector->setAllAssetAccounts()->setLimit($pageSize)->setPage($page)->setTypes($this->transactionTypes); $set = $collector->getPaginatedJournals(); Log::debug(sprintf('Found %d journals to check. ', $set->count())); diff --git a/app/Support/Amount.php b/app/Support/Amount.php index c283289933..1e52f19ef3 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -17,7 +17,6 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; use Illuminate\Support\Collection; -use NumberFormatter; use Preferences as Prefs; /** @@ -43,18 +42,28 @@ class Amount * This method will properly format the given number, in color or "black and white", * as a currency, given two things: the currency required and the current locale. * - * @param TransactionCurrency $format - * @param string $amount - * @param bool $coloured + * @param \FireflyIII\Models\TransactionCurrency $format + * @param string $amount + * @param bool $coloured * * @return string */ public function formatAnything(TransactionCurrency $format, string $amount, bool $coloured = true): string { - $locale = setlocale(LC_MONETARY, 0); - $float = round($amount, 2); - $formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); - $result = $formatter->formatCurrency($float, $format->code); + setlocale(LC_MONETARY, 0); + $float = round($amount, 12); + $info = localeconv(); + $formatted = number_format($float, $format->decimal_places, $info['mon_decimal_point'], $info['mon_thousands_sep']); + + // some complicated switches to format the amount correctly: + $precedes = $amount < 0 ? $info['n_cs_precedes'] : $info['p_cs_precedes']; + $separated = $amount < 0 ? $info['n_sep_by_space'] : $info['p_sep_by_space']; + $space = $separated ? ' ' : ''; + $result = $format->symbol . $space . $formatted; + + if (!$precedes) { + $result = $space . $formatted . $format->symbol; + } if ($coloured === true) { @@ -74,6 +83,22 @@ class Amount return $result; } + /** + * Used in many places (unfortunately). + * + * @param string $currencyCode + * @param string $amount + * @param bool $coloured + * + * @return string + */ + public function formatByCode(string $currencyCode, string $amount, bool $coloured = true): string + { + $currency = TransactionCurrency::whereCode($currencyCode)->first(); + + return $this->formatAnything($currency, $amount, $coloured); + } + /** * * @param \FireflyIII\Models\TransactionJournal $journal @@ -83,27 +108,9 @@ class Amount */ public function formatJournal(TransactionJournal $journal, bool $coloured = true): string { - $locale = setlocale(LC_MONETARY, 0); - $float = round(TransactionJournal::amount($journal), 2); - $formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); - $currencyCode = $journal->transaction_currency_code ?? $journal->transactionCurrency->code; - $result = $formatter->formatCurrency($float, $currencyCode); + $currency = $journal->transactionCurrency; - if ($coloured === true && $float === 0.00) { - return '' . $result . ''; // always grey. - } - if (!$coloured) { - return $result; - } - if (!$journal->isTransfer()) { - if ($float > 0) { - return '' . $result . ''; - } - - return '' . $result . ''; - } else { - return '' . $result . ''; - } + return $this->formatAnything($currency, TransactionJournal::amount($journal), $coloured); } /** @@ -119,41 +126,6 @@ class Amount return $this->formatAnything($currency, strval($transaction->amount), $coloured); } - /** - * This method will properly format the given number, in color or "black and white", - * as a currency, given two things: the currency required and the currency code. - * - * @param string $code - * @param string $amount - * @param bool $coloured - * - * @return string - */ - public function formatWithCode(string $code, string $amount, bool $coloured = true): string - { - $locale = setlocale(LC_MONETARY, 0); - $float = round($amount, 2); - $formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); - $result = $formatter->formatCurrency($float, $code); - - if ($coloured === true) { - - if ($amount > 0) { - return sprintf('%s', $result); - } else { - if ($amount < 0) { - return sprintf('%s', $result); - } - } - - return sprintf('%s', $result); - - - } - - return $result; - } - /** * @return Collection */ diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index 50e3398f26..258da0331b 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -506,7 +506,7 @@ class ExpandedForm // make sure value is formatted nicely: if (!is_null($value) && $value !== '') { - $value = round($value, 2); + $value = round($value, $defaultCurrency->decimal_places); } diff --git a/app/Support/FireflyConfig.php b/app/Support/FireflyConfig.php index 9e42c6de17..743dc40c2a 100644 --- a/app/Support/FireflyConfig.php +++ b/app/Support/FireflyConfig.php @@ -49,11 +49,8 @@ class FireflyConfig */ public function get($name, $default = null) { - Log::debug('Now in FFConfig::get()', ['name' => $name]); $fullName = 'ff-config-' . $name; if (Cache::has($fullName)) { - Log::debug('Return cache.'); - return Cache::get($fullName); } @@ -61,22 +58,15 @@ class FireflyConfig if ($config) { Cache::forever($fullName, $config); - Log::debug('Return found one.'); return $config; } // no preference found and default is null: if (is_null($default)) { - // return NULL - Log::debug('Return null.'); - return null; } - Log::debug('Return this->set().'); - return $this->set($name, $default); - } /** diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php index 6a6e360e78..189b48437e 100644 --- a/app/Support/Navigation.php +++ b/app/Support/Navigation.php @@ -243,28 +243,6 @@ class Navigation throw new FireflyException(sprintf('No date formats for frequency "%s"!', $repeatFrequency)); } - /** - * If the date difference between start and end is less than a month, method returns "endOfDay". If the difference is less than a year, - * method returns "endOfMonth". If the date difference is larger, method returns "endOfYear". - * - * @param \Carbon\Carbon $start - * @param \Carbon\Carbon $end - * - * @return string - */ - public function preferredEndOfPeriod(Carbon $start, Carbon $end): string - { - $format = 'endOfDay'; - if ($start->diffInMonths($end) > 1) { - $format = 'endOfMonth'; - } - - if ($start->diffInMonths($end) > 12) { - $format = 'endOfYear'; - } - return $format; - } - /** * If the date difference between start and end is less than a month, method returns "Y-m-d". If the difference is less than a year, * method returns "Y-m". If the date difference is larger, method returns "Y". @@ -284,6 +262,7 @@ class Navigation if ($start->diffInMonths($end) > 12) { $format = 'Y'; } + return $format; } @@ -311,6 +290,29 @@ class Navigation } + /** + * If the date difference between start and end is less than a month, method returns "endOfDay". If the difference is less than a year, + * method returns "endOfMonth". If the date difference is larger, method returns "endOfYear". + * + * @param \Carbon\Carbon $start + * @param \Carbon\Carbon $end + * + * @return string + */ + public function preferredEndOfPeriod(Carbon $start, Carbon $end): string + { + $format = 'endOfDay'; + if ($start->diffInMonths($end) > 1) { + $format = 'endOfMonth'; + } + + if ($start->diffInMonths($end) > 12) { + $format = 'endOfYear'; + } + + return $format; + } + /** * If the date difference between start and end is less than a month, method returns "1D". If the difference is less than a year, * method returns "1M". If the date difference is larger, method returns "1Y". diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php index d011bdd0fd..c71e1e2985 100644 --- a/app/Support/Search/Search.php +++ b/app/Support/Search/Search.php @@ -14,7 +14,7 @@ declare(strict_types = 1); namespace FireflyIII\Support\Search; -use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Budget; @@ -163,7 +163,8 @@ class Search implements SearchInterface $page = 1; $result = new Collection(); do { - $collector = new JournalCollector($this->user); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class, [$this->user]); $collector->setAllAssetAccounts()->setLimit($pageSize)->setPage($page); $set = $collector->getPaginatedJournals(); Log::debug(sprintf('Found %d journals to check. ', $set->count())); diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php index 65aac8e818..757264a240 100644 --- a/app/Support/Twig/Journal.php +++ b/app/Support/Twig/Journal.php @@ -14,10 +14,8 @@ declare(strict_types = 1); namespace FireflyIII\Support\Twig; -use Amount; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; -use FireflyIII\Models\Budget as ModelBudget; use FireflyIII\Models\Category; use FireflyIII\Models\TransactionJournal; use FireflyIII\Support\CacheProperties; @@ -33,96 +31,6 @@ use Twig_SimpleFunction; class Journal extends Twig_Extension { - /** - * @return Twig_SimpleFunction - */ - public function formatAccountPerspective(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'formatAccountPerspective', function (TransactionJournal $journal, Account $account) { - - $cache = new CacheProperties; - $cache->addProperty('formatAccountPerspective'); - $cache->addProperty($journal->id); - $cache->addProperty($account->id); - - if ($cache->has()) { - return $cache->get(); - } - - // get the account amount: - $transactions = $journal->transactions()->where('transactions.account_id', $account->id)->get(['transactions.*']); - $amount = '0'; - foreach ($transactions as $transaction) { - $amount = bcadd($amount, strval($transaction->amount)); - } - if ($journal->isTransfer()) { - $amount = bcmul($amount, '-1'); - } - - // check if this sum is the same as the journal: - $journalSum = TransactionJournal::amount($journal); - $full = Amount::formatJournal($journal); - if (bccomp($journalSum, $amount) === 0 || bccomp(bcmul($journalSum, '-1'), $amount) === 0) { - $cache->store($full); - - return $full; - } - - $formatted = Amount::format($amount, true); - - if ($journal->isTransfer()) { - $formatted = '' . Amount::format($amount) . ''; - } - $str = $formatted . ' (' . $full . ')'; - $cache->store($str); - - return $str; - - } - ); - } - - /** - * @return Twig_SimpleFunction - */ - public function formatBudgetPerspective(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'formatBudgetPerspective', function (TransactionJournal $journal, ModelBudget $budget) { - - $cache = new CacheProperties; - $cache->addProperty('formatBudgetPerspective'); - $cache->addProperty($journal->id); - $cache->addProperty($budget->id); - - if ($cache->has()) { - return $cache->get(); - } - - // get the account amount: - $transactions = $journal->transactions()->where('transactions.amount', '<', 0)->get(['transactions.*']); - $amount = '0'; - foreach ($transactions as $transaction) { - $currentBudget = $transaction->budgets->first(); - if (!is_null($currentBudget) && $currentBudget->id === $budget->id) { - $amount = bcadd($amount, strval($transaction->amount)); - } - } - if ($amount === '0') { - $formatted = Amount::formatJournal($journal); - $cache->store($formatted); - - return $formatted; - } - - $formatted = Amount::format($amount, true) . ' (' . Amount::formatJournal($journal) . ')'; - $cache->store($formatted); - - return $formatted; - } - ); - } /** * @return Twig_SimpleFunction @@ -178,8 +86,6 @@ class Journal extends Twig_Extension $functions = [ $this->getSourceAccount(), $this->getDestinationAccount(), - $this->formatAccountPerspective(), - $this->formatBudgetPerspective(), $this->journalBudgets(), $this->journalCategories(), ]; @@ -253,12 +159,12 @@ class Journal extends Twig_Extension $budgets = []; // get all budgets: foreach ($journal->budgets as $budget) { - $budgets[] = sprintf('%1$s', e($budget->name), route('accounts.show', $budget->id)); + $budgets[] = sprintf('%1$s', e($budget->name), route('budgets.show', $budget->id)); } // and more! foreach ($journal->transactions as $transaction) { foreach ($transaction->budgets as $budget) { - $budgets[] = sprintf('%1$s', e($budget->name), route('accounts.show', $budget->id)); + $budgets[] = sprintf('%1$s', e($budget->name), route('budgets.show', $budget->id)); } } $string = join(', ', array_unique($budgets)); @@ -288,7 +194,7 @@ class Journal extends Twig_Extension $categories = []; // get all categories for the journal itself (easy): foreach ($journal->categories as $category) { - $categories[] = sprintf('%1$s', e($category->name), route('accounts.show', $category->id)); + $categories[] = sprintf('%1$s', e($category->name), route('categories.show', $category->id)); } if (count($categories) === 0) { $set = Category::distinct()->leftJoin('category_transaction', 'categories.id', '=', 'category_transaction.category_id') @@ -299,7 +205,7 @@ class Journal extends Twig_Extension ->get(['categories.*']); /** @var Category $category */ foreach ($set as $category) { - $categories[] = sprintf('%1$s', e($category->name), route('accounts.show', $category->id)); + $categories[] = sprintf('%1$s', e($category->name), route('categories.show', $category->id)); } } diff --git a/app/Support/Twig/Transaction.php b/app/Support/Twig/Transaction.php index 0e9d851c40..5285a8ee90 100644 --- a/app/Support/Twig/Transaction.php +++ b/app/Support/Twig/Transaction.php @@ -17,6 +17,7 @@ use Amount; use Crypt; use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction as TransactionModel; +use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionType; use Twig_Extension; use Twig_SimpleFilter; @@ -29,15 +30,16 @@ use Twig_SimpleFunction; */ class Transaction extends Twig_Extension { + /** * @return Twig_SimpleFunction */ - public function formatAmountPlainWithCode(): Twig_SimpleFunction + public function formatAnything(): Twig_SimpleFunction { return new Twig_SimpleFunction( - 'formatAmountPlainWithCode', function (string $amount, string $code): string { + 'formatAnything', function (TransactionCurrency $currency, string $amount): string { - return Amount::formatWithCode($code, $amount, false); + return Amount::formatAnything($currency, $amount, true); }, ['is_safe' => ['html']] ); @@ -46,12 +48,26 @@ class Transaction extends Twig_Extension /** * @return Twig_SimpleFunction */ - public function formatAmountWithCode(): Twig_SimpleFunction + public function formatAnythingPlain(): Twig_SimpleFunction { return new Twig_SimpleFunction( - 'formatAmountWithCode', function (string $amount, string $code): string { + 'formatAnythingPlain', function (TransactionCurrency $currency, string $amount): string { - return Amount::formatWithCode($code, $amount, true); + return Amount::formatAnything($currency, $amount, false); + + }, ['is_safe' => ['html']] + ); + } + + /** + * @return Twig_SimpleFunction + */ + public function formatByCode(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'formatByCode', function (string $currencyCode, string $amount): string { + + return Amount::formatByCode($currencyCode, $amount, true); }, ['is_safe' => ['html']] ); @@ -75,8 +91,8 @@ class Transaction extends Twig_Extension public function getFunctions(): array { $functions = [ - $this->formatAmountWithCode(), - $this->formatAmountPlainWithCode(), + $this->formatAnything(), + $this->formatAnythingPlain(), $this->transactionSourceAccount(), $this->transactionDestinationAccount(), $this->optionalJournalAmount(), @@ -85,6 +101,7 @@ class Transaction extends Twig_Extension $this->transactionCategories(), $this->transactionIdCategories(), $this->splitJournalIndicator(), + $this->formatByCode(), ]; return $functions; @@ -107,24 +124,17 @@ class Transaction extends Twig_Extension { return new Twig_SimpleFunction( 'optionalJournalAmount', function (int $journalId, string $transactionAmount, string $code, string $type): string { - - $amount = strval( - TransactionModel::where('transaction_journal_id', $journalId) - ->whereNull('deleted_at') - ->where('amount', '<', 0) - ->sum('amount') - ); - + // get amount of journal: + $amount = strval(TransactionModel::where('transaction_journal_id', $journalId)->whereNull('deleted_at')->where('amount', '<', 0)->sum('amount')); + // display deposit and transfer positive if ($type === TransactionType::DEPOSIT || $type === TransactionType::TRANSFER) { $amount = bcmul($amount, '-1'); } - if ( - bccomp($amount, $transactionAmount) !== 0 - && bccomp($amount, bcmul($transactionAmount, '-1')) !== 0 - ) { - // not equal? - return sprintf(' (%s)', Amount::formatWithCode($code, $amount, true)); + // not equal to transaction amount? + if (bccomp($amount, $transactionAmount) !== 0 && bccomp($amount, bcmul($transactionAmount, '-1')) !== 0) { + //$currency = + return sprintf(' (%s)', Amount::formatByCode($code, $amount, true)); } return ''; @@ -327,6 +337,21 @@ class Transaction extends Twig_Extension ); } + /** + * @param int $isEncrypted + * @param string $value + * + * @return string + */ + private function encrypted(int $isEncrypted, string $value): string + { + if ($isEncrypted === 1) { + return Crypt::decrypt($value); + } + + return $value; + } + /** * @param TransactionModel $transaction * @@ -334,6 +359,20 @@ class Transaction extends Twig_Extension */ private function getTransactionBudgets(TransactionModel $transaction): string { + // journal has a budget: + if (isset($transaction->transaction_journal_budget_id)) { + $name = $this->encrypted(intval($transaction->transaction_journal_budget_encrypted), $transaction->transaction_journal_budget_name); + + return sprintf('%s', route('budgets.show', [$transaction->transaction_journal_budget_id]), $name, $name); + } + + // transaction has a budget + if (isset($transaction->transaction_budget_id)) { + $name = $this->encrypted(intval($transaction->transaction_budget_encrypted), $transaction->transaction_budget_name); + + return sprintf('%s', route('budgets.show', [$transaction->transaction_budget_id]), $name, $name); + } + // see if the transaction has a budget: $budgets = $transaction->budgets()->get(); if ($budgets->count() === 0) { @@ -359,6 +398,20 @@ class Transaction extends Twig_Extension */ private function getTransactionCategories(TransactionModel $transaction): string { + // journal has a category: + if (isset($transaction->transaction_journal_category_id)) { + $name = $this->encrypted(intval($transaction->transaction_journal_category_encrypted), $transaction->transaction_journal_category_name); + + return sprintf('%s', route('categories.show', [$transaction->transaction_journal_category_id]), $name, $name); + } + + // transaction has a category: + if (isset($transaction->transaction_category_id)) { + $name = $this->encrypted(intval($transaction->transaction_category_encrypted), $transaction->transaction_category_name); + + return sprintf('%s', route('categories.show', [$transaction->transaction_category_id]), $name, $name); + } + // see if the transaction has a category: $categories = $transaction->categories()->get(); if ($categories->count() === 0) { diff --git a/app/User.php b/app/User.php index 763d33d92c..06a150ef49 100644 --- a/app/User.php +++ b/app/User.php @@ -51,15 +51,6 @@ class User extends Authenticatable */ protected $table = 'users'; - - /** - * @return HasMany - */ - public function availableBudgets(): HasMany - { - return $this->hasMany('FireflyIII\Models\AvailableBudget'); - } - /** * @return HasMany */ @@ -96,6 +87,14 @@ class User extends Authenticatable return $this->hasMany('FireflyIII\Models\Attachment'); } + /** + * @return HasMany + */ + public function availableBudgets(): HasMany + { + return $this->hasMany('FireflyIII\Models\AvailableBudget'); + } + /** * @return HasMany */ diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index 9555f3e00c..778996bb88 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -53,6 +53,7 @@ class FireflyValidator extends Validator } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @param $attribute * @param $value * @@ -71,6 +72,7 @@ class FireflyValidator extends Validator } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @param $attribute * @param $value * @param $parameters @@ -94,6 +96,7 @@ class FireflyValidator extends Validator } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @param $attribute * @param $value * @@ -115,6 +118,7 @@ class FireflyValidator extends Validator } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @param $attribute * @param $value * @@ -144,6 +148,7 @@ class FireflyValidator extends Validator } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @param $attribute * @param $value * @param $parameters @@ -256,6 +261,7 @@ class FireflyValidator extends Validator } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @param $attribute * @param $value * @param $parameters @@ -286,6 +292,7 @@ class FireflyValidator extends Validator } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @param $attribute * @param $value * @param $parameters @@ -319,30 +326,7 @@ class FireflyValidator extends Validator } /** - * @param $attribute - * @param $value - * @param $parameters - * - * - * @return bool - */ - public function validateUniqueForUser($attribute, $value, $parameters): bool - { - $query = DB::table($parameters[0])->where($parameters[1], $value); - $query->where('user_id', auth()->user()->id); - if (isset($parameters[2])) { - $query->where('id', '!=', $parameters[2]); - } - $count = $query->count(); - if ($count == 0) { - return true; - } - - return false; - - } - - /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * Validate an object and its unicity. Checks for encryption / encrypted values as well. * * parameter 0: the table @@ -380,6 +364,7 @@ class FireflyValidator extends Validator } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @param $attribute * @param $value * @param $parameters diff --git a/bootstrap/app.php b/bootstrap/app.php index 06a88210b2..88ef952626 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -23,7 +23,7 @@ declare(strict_types = 1); | */ -bcscale(6); +bcscale(12); $app = new Illuminate\Foundation\Application( diff --git a/composer.json b/composer.json index 27a784e1bf..7c6b72dab5 100755 --- a/composer.json +++ b/composer.json @@ -15,13 +15,13 @@ "management" ], "license": "MIT", - "homepage": "https://github.com/JC5/firefly-iii", + "homepage": "https://github.com/firefly-iii/firefly-iii", "type": "project", "authors": [ { "name": "James Cole", "email": "thegrumpydictator@gmail.com", - "homepage": "https://github.com/JC5", + "homepage": "https://github.com/firefly-iii", "role": "Developer" } ], @@ -72,9 +72,6 @@ ], "post-install-cmd": [ "Illuminate\\Foundation\\ComposerScripts::postInstall", - "php artisan firefly:upgrade-instructions", - "php artisan firefly:upgrade-database", - "php artisan firefly:verify", "php artisan firefly:github-move", "php artisan optimize" ], diff --git a/composer.lock b/composer.lock index 2c128e7a1a..cee08cb19e 100644 --- a/composer.lock +++ b/composer.lock @@ -240,16 +240,16 @@ }, { "name": "doctrine/annotations", - "version": "v1.3.0", + "version": "v1.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "30e07cf03edc3cd3ef579d0dd4dd8c58250799a5" + "reference": "bd4461328621bde0ae6b1b2675fbc6aca4ceb558" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/30e07cf03edc3cd3ef579d0dd4dd8c58250799a5", - "reference": "30e07cf03edc3cd3ef579d0dd4dd8c58250799a5", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/bd4461328621bde0ae6b1b2675fbc6aca4ceb558", + "reference": "bd4461328621bde0ae6b1b2675fbc6aca4ceb558", "shasum": "" }, "require": { @@ -304,7 +304,7 @@ "docblock", "parser" ], - "time": "2016-10-24T11:45:47+00:00" + "time": "2016-12-30T15:59:45+00:00" }, { "name": "doctrine/cache", @@ -1888,23 +1888,24 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v5.4.4", + "version": "v5.4.5", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "545ce9136690cea74f98f86fbb9c92dd9ab1a756" + "reference": "cd142238a339459b10da3d8234220963f392540c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/545ce9136690cea74f98f86fbb9c92dd9ab1a756", - "reference": "545ce9136690cea74f98f86fbb9c92dd9ab1a756", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/cd142238a339459b10da3d8234220963f392540c", + "reference": "cd142238a339459b10da3d8234220963f392540c", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { - "mockery/mockery": "~0.9.1" + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.2" }, "type": "library", "extra": { @@ -1937,7 +1938,7 @@ "mail", "mailer" ], - "time": "2016-11-24T01:01:23+00:00" + "time": "2016-12-29T10:02:40+00:00" }, { "name": "symfony/console", @@ -3871,16 +3872,16 @@ }, { "name": "phpunit/phpunit", - "version": "5.7.4", + "version": "5.7.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "af91da3f2671006ff5d0628023de3b7ac4d1ef09" + "reference": "50fd2be8f3e23e91da825f36f08e5f9633076ffe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/af91da3f2671006ff5d0628023de3b7ac4d1ef09", - "reference": "af91da3f2671006ff5d0628023de3b7ac4d1ef09", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/50fd2be8f3e23e91da825f36f08e5f9633076ffe", + "reference": "50fd2be8f3e23e91da825f36f08e5f9633076ffe", "shasum": "" }, "require": { @@ -3949,7 +3950,7 @@ "testing", "xunit" ], - "time": "2016-12-13T16:19:44+00:00" + "time": "2016-12-28T07:18:51+00:00" }, { "name": "phpunit/phpunit-mock-objects", diff --git a/config/filesystems.php b/config/filesystems.php index cac819789e..ba8b6b6eb9 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -73,6 +73,11 @@ return [ 'driver' => 'local', 'root' => base_path('resources/seeds'), ], + 'stubs' => [ + 'driver' => 'local', + 'root' => base_path('resources/stubs'), + ], + 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), diff --git a/config/firefly.php b/config/firefly.php index ef2ff2280b..45e813e6eb 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -19,17 +19,11 @@ declare(strict_types = 1); return [ 'configuration' => [ - 'single_user_mode' => true, - 'is_demo_site' => false, - 'must_confirm_account' => false, - 'mail_for_lockout' => false, - 'mail_for_blocked_domain' => false, - 'mail_for_blocked_email' => false, - 'mail_for_bad_login' => false, - 'mail_for_blocked_login' => false, + 'single_user_mode' => true, + 'is_demo_site' => false, ], 'chart' => 'chartjs', - 'version' => '4.3.0', + 'version' => '4.3.1', 'csv_import_enabled' => true, 'maxUploadSize' => 5242880, 'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'], @@ -112,13 +106,15 @@ return [ 'Cash account' => 'cash', ], 'languages' => [ - 'de_DE' => ['name_locale' => 'Deutsch', 'name_english' => 'German', 'complete' => false], + 'de_DE' => ['name_locale' => 'Deutsch', 'name_english' => 'German', 'complete' => true], 'en_US' => ['name_locale' => 'English', 'name_english' => 'English', 'complete' => true], 'es_ES' => ['name_locale' => 'Español', 'name_english' => 'Spanish', 'complete' => false], 'fr_FR' => ['name_locale' => 'Français', 'name_english' => 'French', 'complete' => false], 'hr_HR' => ['name_locale' => 'hrvatski', 'name_english' => 'Croatian', 'complete' => false], 'nl_NL' => ['name_locale' => 'Nederlands', 'name_english' => 'Dutch', 'complete' => true], + 'pl_PL' => ['name_locale' => 'Polski', 'name_english' => 'Polish ', 'complete' => false], 'pt_BR' => ['name_locale' => 'Português do Brasil', 'name_english' => 'Portuguese (Brazil)', 'complete' => true], + 'ru-RU' => ['name_locale' => 'Russian', 'name_english' => 'Russian', 'complete' => false], 'zh-HK' => ['name_locale' => '繁體中文(香港)', 'name_english' => 'Chinese Traditional, Hong Kong', 'complete' => false], 'zh-TW' => ['name_locale' => '正體中文', 'name_english' => 'Chinese Traditional', 'complete' => false], ], @@ -148,6 +144,7 @@ return [ 'transaction_type' => 'FireflyIII\Models\TransactionType', 'currency' => 'FireflyIII\Models\TransactionCurrency', 'limitrepetition' => 'FireflyIII\Models\LimitRepetition', + 'budgetlimit' => 'FireflyIII\Models\BudgetLimit', 'piggyBank' => 'FireflyIII\Models\PiggyBank', 'tj' => 'FireflyIII\Models\TransactionJournal', 'unfinishedJournal' => 'FireflyIII\Support\Binder\UnfinishedJournal', diff --git a/config/session.php b/config/session.php index 51698cb45e..f5af7f0fdc 100644 --- a/config/session.php +++ b/config/session.php @@ -13,179 +13,18 @@ declare(strict_types = 1); return [ - - /* - |-------------------------------------------------------------------------- - | Default Session Driver - |-------------------------------------------------------------------------- - | - | This option controls the default session "driver" that will be used on - | requests. By default, we will use the lightweight native driver but - | you may specify any of the other wonderful drivers provided here. - | - | Supported: "file", "cookie", "database", "apc", - | "memcached", "redis", "array" - | - */ - - 'driver' => env('SESSION_DRIVER', 'file'), - - /* - |-------------------------------------------------------------------------- - | Session Lifetime - |-------------------------------------------------------------------------- - | - | Here you may specify the number of minutes that you wish the session - | to be allowed to remain idle before it expires. If you want them - | to immediately expire on the browser closing, set that option. - | - */ - - 'lifetime' => 120, - + 'driver' => env('SESSION_DRIVER', 'file'), + 'lifetime' => 120, 'expire_on_close' => false, - - /* - |-------------------------------------------------------------------------- - | Session Encryption - |-------------------------------------------------------------------------- - | - | This option allows you to easily specify that all of your session data - | should be encrypted before it is stored. All encryption will be run - | automatically by Laravel and you can use the Session like normal. - | - */ - - 'encrypt' => true, - - /* - |-------------------------------------------------------------------------- - | Session File Location - |-------------------------------------------------------------------------- - | - | When using the native session driver, we need a location where session - | files may be stored. A default has been set for you but a different - | location may be specified. This is only needed for file sessions. - | - */ - - 'files' => storage_path('framework/sessions'), - - /* - |-------------------------------------------------------------------------- - | Session Database Connection - |-------------------------------------------------------------------------- - | - | When using the "database" or "redis" session drivers, you may specify a - | connection that should be used to manage these sessions. This should - | correspond to a connection in your database configuration options. - | - */ - - 'connection' => null, - - /* - |-------------------------------------------------------------------------- - | Session Database Table - |-------------------------------------------------------------------------- - | - | When using the "database" session driver, you may specify the table we - | should use to manage the sessions. Of course, a sensible default is - | provided for you; however, you are free to change this as needed. - | - */ - - 'table' => 'sessions', - - /* - |-------------------------------------------------------------------------- - | Session Cache Store - |-------------------------------------------------------------------------- - | - | When using the "apc" or "memcached" session drivers, you may specify a - | cache store that should be used for these sessions. This value must - | correspond with one of the application's configured cache stores. - | - */ - - 'store' => null, - - /* - |-------------------------------------------------------------------------- - | Session Sweeping Lottery - |-------------------------------------------------------------------------- - | - | Some session drivers must manually sweep their storage location to get - | rid of old sessions from storage. Here are the chances that it will - | happen on a given request. By default, the odds are 2 out of 100. - | - */ - - 'lottery' => [2, 100], - - /* - |-------------------------------------------------------------------------- - | Session Cookie Name - |-------------------------------------------------------------------------- - | - | Here you may change the name of the cookie used to identify a session - | instance by ID. The name specified here will get used every time a - | new session cookie is created by the framework for every driver. - | - */ - - 'cookie' => 'firefly_session', - - /* - |-------------------------------------------------------------------------- - | Session Cookie Path - |-------------------------------------------------------------------------- - | - | The session cookie path determines the path for which the cookie will - | be regarded as available. Typically, this will be the root path of - | your application but you are free to change this when necessary. - | - */ - - 'path' => env('COOKIE_PATH', '/'), - - /* - |-------------------------------------------------------------------------- - | Session Cookie Domain - |-------------------------------------------------------------------------- - | - | Here you may change the domain of the cookie used to identify a session - | in your application. This will determine which domains the cookie is - | available to in your application. A sensible default has been set. - | - */ - - 'domain' => env('COOKIE_DOMAIN', null), - - /* - |-------------------------------------------------------------------------- - | HTTPS Only Cookies - |-------------------------------------------------------------------------- - | - | By setting this option to true, session cookies will only be sent back - | to the server if the browser has a HTTPS connection. This will keep - | the cookie from being sent to you if it can not be done securely. - | - */ - - 'secure' => env('COOKIE_SECURE', false), - - /* - |-------------------------------------------------------------------------- - | HTTP Access Only - |-------------------------------------------------------------------------- - | - | Setting this value to true will prevent JavaScript from accessing the - | value of the cookie and the cookie will only be accessible through - | the HTTP protocol. You are free to modify this option if needed. - | - */ - - 'http_only' => !env('COOKIE_SECURE', false), - + 'encrypt' => true, + 'files' => storage_path('framework/sessions'), + 'connection' => null, + 'table' => 'sessions', + 'store' => null, + 'lottery' => [2, 100], + 'cookie' => 'firefly_session', + 'path' => env('COOKIE_PATH', '/'), + 'domain' => env('COOKIE_DOMAIN', null), + 'secure' => env('COOKIE_SECURE', false), + 'http_only' => true, ]; diff --git a/config/upgrade.php b/config/upgrade.php index a36422fe29..e88f92c76e 100644 --- a/config/upgrade.php +++ b/config/upgrade.php @@ -12,12 +12,5 @@ declare(strict_types = 1); return [ - 'text' => [ - '3.7' => 'Because of the upgrade to Laravel 5.2, several manual changes must be made to your Firefly III installation. ' . - 'Please follow the instructions on the following page: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-3.7.0', - '3.8' => 'This version of Firefly III requires PHP 7.0.', - '3.10' => 'Please find the full upgrade instructions here: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-3.10', - '4.0' => 'Please find the full upgrade instructions here: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-4.0', - '4.1' => 'Please find the full upgrade instructions here: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-4.0', - ], + 'text' => [], ]; diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index c889b7df44..8a65c4ee73 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -26,7 +26,6 @@ $factory->define( static $password; return [ - 'name' => $faker->name, 'email' => $faker->safeEmail, 'password' => $password ?: $password = bcrypt('secret'), 'remember_token' => str_random(10), diff --git a/database/migrations/2016_06_16_000002_create_main_tables.php b/database/migrations/2016_06_16_000002_create_main_tables.php index ddbcd339bc..1090bcb05f 100644 --- a/database/migrations/2016_06_16_000002_create_main_tables.php +++ b/database/migrations/2016_06_16_000002_create_main_tables.php @@ -89,7 +89,7 @@ class CreateMainTables extends Migration $table->integer('user_id', false, true); $table->integer('account_type_id', false, true); $table->string('name', 1024); - $table->decimal('virtual_balance', 14, 4)->nullable(); + $table->decimal('virtual_balance', 22, 12)->nullable(); $table->string('iban', 255)->nullable(); $table->boolean('active')->default(1); $table->boolean('encrypted')->default(0); @@ -160,8 +160,8 @@ class CreateMainTables extends Migration $table->integer('user_id', false, true); $table->string('name', 1024); $table->string('match', 1024); - $table->decimal('amount_min', 14, 4); - $table->decimal('amount_max', 14, 4); + $table->decimal('amount_min', 22, 12); + $table->decimal('amount_max', 22, 12); $table->date('date'); $table->string('repeat_freq', 30); $table->smallInteger('skip', false, true)->default(0); @@ -205,7 +205,7 @@ class CreateMainTables extends Migration $table->timestamps(); $table->integer('budget_id', false, true); $table->date('startdate'); - $table->decimal('amount', 14, 4); + $table->decimal('amount', 22, 12); $table->string('repeat_freq', 30); $table->boolean('repeats')->default(0); $table->foreign('budget_id')->references('id')->on('budgets')->onDelete('cascade'); @@ -221,7 +221,7 @@ class CreateMainTables extends Migration $table->integer('budget_limit_id', false, true); $table->date('startdate'); $table->date('enddate'); - $table->decimal('amount', 14, 4); + $table->decimal('amount', 22, 12); $table->foreign('budget_limit_id')->references('id')->on('budget_limits')->onDelete('cascade'); } ); @@ -299,7 +299,7 @@ class CreateMainTables extends Migration $table->softDeletes(); $table->integer('account_id', false, true); $table->string('name', 1024); - $table->decimal('targetamount', 14, 4); + $table->decimal('targetamount', 22, 12); $table->date('startdate')->nullable(); $table->date('targetdate')->nullable(); $table->integer('order', false, true)->default(0); @@ -318,7 +318,7 @@ class CreateMainTables extends Migration $table->integer('piggy_bank_id', false, true); $table->date('startdate')->nullable(); $table->date('targetdate')->nullable(); - $table->decimal('currentamount', 14, 4); + $table->decimal('currentamount', 22, 12); $table->foreign('piggy_bank_id')->references('id')->on('piggy_banks')->onDelete('cascade'); } ); @@ -472,8 +472,8 @@ class CreateMainTables extends Migration $table->string('tagMode', 1024); $table->date('date')->nullable(); $table->text('description')->nullable(); - $table->decimal('latitude', 18, 12)->nullable(); - $table->decimal('longitude', 18, 12)->nullable(); + $table->decimal('latitude', 24, 12)->nullable(); + $table->decimal('longitude', 24, 12)->nullable(); $table->boolean('zoomLevel')->nullable(); // link user id to users table @@ -579,7 +579,7 @@ class CreateMainTables extends Migration $table->integer('piggy_bank_id', false, true); $table->integer('transaction_journal_id', false, true)->nullable(); $table->date('date'); - $table->decimal('amount', 14, 4); + $table->decimal('amount', 22, 12); $table->foreign('piggy_bank_id')->references('id')->on('piggy_banks')->onDelete('cascade'); $table->foreign('transaction_journal_id')->references('id')->on('transaction_journals')->onDelete('set null'); @@ -596,7 +596,7 @@ class CreateMainTables extends Migration $table->integer('account_id', false, true); $table->integer('transaction_journal_id', false, true); $table->string('description', 1024)->nullable(); - $table->decimal('amount', 14, 4); + $table->decimal('amount', 22, 12); $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $table->foreign('transaction_journal_id')->references('id')->on('transaction_journals')->onDelete('cascade'); diff --git a/database/migrations/2016_12_22_150431_changes_for_v430.php b/database/migrations/2016_12_22_150431_changes_for_v430.php index a01c462713..84f61fb4c1 100644 --- a/database/migrations/2016_12_22_150431_changes_for_v430.php +++ b/database/migrations/2016_12_22_150431_changes_for_v430.php @@ -30,7 +30,7 @@ class ChangesForV430 extends Migration $table->softDeletes(); $table->integer('user_id', false, true); $table->integer('transaction_currency_id', false, true); - $table->decimal('amount', 14, 4); + $table->decimal('amount', 22, 12); $table->date('start_date'); $table->date('end_date'); diff --git a/database/migrations/2016_12_28_203205_changes_for_v431.php b/database/migrations/2016_12_28_203205_changes_for_v431.php new file mode 100644 index 0000000000..310ccac5a8 --- /dev/null +++ b/database/migrations/2016_12_28_203205_changes_for_v431.php @@ -0,0 +1,97 @@ +string('repeat_freq', 30)->nullable(); + } + ); + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->boolean('repeats')->default(0); + } + ); + + + // remove date field "end_date" + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->dropColumn('end_date'); + } + ); + + // change field "start_date" to "startdate" +// Schema::table( +// 'budget_limits', function (Blueprint $table) { +// $table->renameColumn('startdate', 'start_date'); +// } +// ); + + } + + /** + * Run the migrations. + * + * @SuppressWarnings(PHPMD.ShortMethodName) + */ + public function up() + { + // add decimal places to "transaction currencies". + Schema::table( + 'transaction_currencies', function (Blueprint $table) { + $table->smallInteger('decimal_places', false, true)->default(2); + } + ); + + // change field "startdate" to "start_date" + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->renameColumn('startdate', 'start_date'); + } + ); + + // add date field "end_date" after "start_date" + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->date('end_date')->nullable()->after('start_date'); + } + ); + + // drop "repeats" and "repeat_freq". + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->dropColumn('repeats'); + + } + ); + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->dropColumn('repeat_freq'); + } + ); + + + } +} diff --git a/database/seeds/TransactionCurrencySeeder.php b/database/seeds/TransactionCurrencySeeder.php index 6edd351d28..f1204569a3 100644 --- a/database/seeds/TransactionCurrencySeeder.php +++ b/database/seeds/TransactionCurrencySeeder.php @@ -23,12 +23,13 @@ class TransactionCurrencySeeder extends Seeder { DB::table('transaction_currencies')->delete(); - TransactionCurrency::create(['code' => 'EUR', 'name' => 'Euro', 'symbol' => '€']); - TransactionCurrency::create(['code' => 'USD', 'name' => 'US Dollar', 'symbol' => '$']); - TransactionCurrency::create(['code' => 'HUF', 'name' => 'Hungarian forint', 'symbol' => 'Ft']); - TransactionCurrency::create(['code' => 'BRL', 'name' => 'Real', 'symbol' => 'R$']); - TransactionCurrency::create(['code' => 'GBP', 'name' => 'British Pound', 'symbol' => '£']); - TransactionCurrency::create(['code' => 'IDR', 'name' => 'Indonesian rupiah', 'symbol' => 'Rp']); + TransactionCurrency::create(['code' => 'EUR', 'name' => 'Euro', 'symbol' => '€', 'decimal_places' => 2]); + TransactionCurrency::create(['code' => 'USD', 'name' => 'US Dollar', 'symbol' => '$', 'decimal_places' => 2]); + TransactionCurrency::create(['code' => 'HUF', 'name' => 'Hungarian forint', 'symbol' => 'Ft', 'decimal_places' => 2]); + TransactionCurrency::create(['code' => 'BRL', 'name' => 'Real', 'symbol' => 'R$', 'decimal_places' => 2]); + TransactionCurrency::create(['code' => 'GBP', 'name' => 'British Pound', 'symbol' => '£', 'decimal_places' => 2]); + TransactionCurrency::create(['code' => 'IDR', 'name' => 'Indonesian rupiah', 'symbol' => 'Rp', 'decimal_places' => 2]); + TransactionCurrency::create(['code' => 'XBT', 'name' => 'Bitcoin', 'symbol' => 'B', 'decimal_places' => 8]); } } diff --git a/public/js/ff/accounts/show.js b/public/js/ff/accounts/show.js index 1d53f11c1b..a1db23e92a 100644 --- a/public/js/ff/accounts/show.js +++ b/public/js/ff/accounts/show.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: chartUri, incomeCategoryUri, expenseCategoryUri, expenseBudgetUri */ + var fixHelper = function (e, tr) { "use strict"; var $originals = tr.children(); @@ -53,8 +55,6 @@ $(function () { } } ).disableSelection(); - } else { - console.log('its null'); } }); @@ -62,7 +62,6 @@ $(function () { function sortStop(event, ui) { "use strict"; var current = $(ui.item); - console.log('sort stop'); var thisDate = current.data('date'); var originalBG = current.css('backgroundColor'); @@ -71,6 +70,7 @@ function sortStop(event, ui) { // animate something with color: current.animate({backgroundColor: "#d9534f"}, 200, function () { $(this).animate({backgroundColor: originalBG}, 200); + return undefined; }); return false; @@ -86,9 +86,11 @@ function sortStop(event, ui) { }); // do extra animation when done? - $.post('transactions/reorder', {items: submit, date: thisDate, _token: token}); + $.post('transactions/reorder', {items: submit, date: thisDate}); current.animate({backgroundColor: "#5cb85c"}, 200, function () { $(this).animate({backgroundColor: originalBG}, 200); + return undefined; }); + return undefined; } diff --git a/public/js/ff/bills/show.js b/public/js/ff/bills/show.js index 3964f90dcb..f67c246ca2 100644 --- a/public/js/ff/bills/show.js +++ b/public/js/ff/bills/show.js @@ -8,7 +8,7 @@ * See the LICENSE file for details. */ -/* global comboChart, billID */ +/** global: billUri */ $(function () { "use strict"; diff --git a/public/js/ff/budgets/index.js b/public/js/ff/budgets/index.js index 95a4eca219..936ae07b7f 100644 --- a/public/js/ff/budgets/index.js +++ b/public/js/ff/budgets/index.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: spent, budgeted, available, currencySymbol */ + function drawSpentBar() { "use strict"; if ($('.spentBar').length > 0) { @@ -70,7 +72,7 @@ function updateBudgetedAmounts(e) { drawBudgetedBar(); // send a post to Firefly to update the amount: - $.post('budgets/amount/' + id, {amount: value, _token: token}).done(function (data) { + $.post('budgets/amount/' + id, {amount: value}).done(function (data) { // update the link if relevant: if (data.repetition > 0) { $('.budget-link[data-id="' + id + '"]').attr('href', 'budgets/show/' + id + '/' + data.repetition); @@ -79,11 +81,6 @@ function updateBudgetedAmounts(e) { } }); } - - - console.log('Budget id is ' + id); - console.log('Difference = ' + (value - original )); - } $(function () { @@ -102,17 +99,6 @@ $(function () { */ $('input[type="number"]').on('input', updateBudgetedAmounts); - - /* - Draw the charts, if necessary: - */ - if (typeof budgetID !== 'undefined' && typeof repetitionID === 'undefined') { - columnChart('chart/budget/' + budgetID, 'budgetOverview'); - } - if (typeof budgetID !== 'undefined' && typeof repetitionID !== 'undefined') { - lineChart('chart/budget/' + budgetID + '/' + repetitionID, 'budgetOverview'); - } - }); function updateIncome() { diff --git a/public/js/ff/budgets/show.js b/public/js/ff/budgets/show.js index 1e7fb668dc..07cf18ed9e 100644 --- a/public/js/ff/budgets/show.js +++ b/public/js/ff/budgets/show.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: budgetChartUri */ + $(function () { "use strict"; diff --git a/public/js/ff/categories/show-by-date.js b/public/js/ff/categories/show-by-date.js index 6b5ee6b775..e97dab4db8 100644 --- a/public/js/ff/categories/show-by-date.js +++ b/public/js/ff/categories/show-by-date.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: specific */ + $(function () { "use strict"; columnChart(specific, 'period-specific-period'); diff --git a/public/js/ff/categories/show.js b/public/js/ff/categories/show.js index 9d14bc1377..e1d5905d24 100644 --- a/public/js/ff/categories/show.js +++ b/public/js/ff/categories/show.js @@ -8,8 +8,11 @@ * See the LICENSE file for details. */ +/** global: all, current, specific */ + $(function () { "use strict"; columnChart(all, 'all'); columnChart(current, 'period'); + columnChart(specific, 'period-specific-period'); }); \ No newline at end of file diff --git a/public/js/ff/charts.defaults.js b/public/js/ff/charts.defaults.js index bbcce69ccb..c670667dd1 100644 --- a/public/js/ff/charts.defaults.js +++ b/public/js/ff/charts.defaults.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: accounting */ + var defaultChartOptions = { scales: { xAxes: [ @@ -20,7 +22,7 @@ var defaultChartOptions = { yAxes: [{ display: true, ticks: { - callback: function (tickValue, index, ticks) { + callback: function (tickValue) { "use strict"; return accounting.formatMoney(tickValue); diff --git a/public/js/ff/charts.js b/public/js/ff/charts.js index 7180a9c131..0e9ff49d21 100644 --- a/public/js/ff/charts.js +++ b/public/js/ff/charts.js @@ -7,7 +7,7 @@ * * See the LICENSE file for details. */ - +/** global: Chart, defaultChartOptions, accounting, defaultPieOptions, noDataForChart */ var allCharts = {}; /* @@ -97,7 +97,7 @@ function doubleYChart(URI, container) { { display: true, ticks: { - callback: function (tickValue, index, ticks) { + callback: function (tickValue) { "use strict"; return accounting.formatMoney(tickValue); @@ -111,7 +111,7 @@ function doubleYChart(URI, container) { { display: true, ticks: { - callback: function (tickValue, index, ticks) { + callback: function (tickValue) { "use strict"; return accounting.formatMoney(tickValue); @@ -125,7 +125,6 @@ function doubleYChart(URI, container) { ]; options.stacked = true; options.scales.xAxes[0].stacked = true; - // console.log(options); var chartType = 'bar'; @@ -148,7 +147,7 @@ function doubleYNonStackedChart(URI, container) { { display: true, ticks: { - callback: function (tickValue, index, ticks) { + callback: function (tickValue) { "use strict"; return accounting.formatMoney(tickValue); @@ -162,7 +161,7 @@ function doubleYNonStackedChart(URI, container) { { display: true, ticks: { - callback: function (tickValue, index, ticks) { + callback: function (tickValue) { "use strict"; return accounting.formatMoney(tickValue); @@ -180,7 +179,6 @@ function doubleYNonStackedChart(URI, container) { } - /** * * @param URI @@ -241,7 +239,6 @@ function pieChart(URI, container) { */ function drawAChart(URI, container, chartType, options, colorData) { if ($('#' + container).length === 0) { - console.log('No container called ' + container + ' was found.'); return; } @@ -250,14 +247,17 @@ function drawAChart(URI, container, chartType, options, colorData) { if (data.labels.length === 0) { - console.log(chartType + " chart in " + container + " has no data."); // remove the chart container + parent var holder = $('#' + container).parent().parent(); - if (holder.hasClass('box')) { + if (holder.hasClass('box') || holder.hasClass('box-body')) { // find box-body: - var boxBody = holder.find('.box-body'); + var boxBody; + if (!holder.hasClass('box-body')) { + boxBody = holder.find('.box-body'); + } else { + boxBody = holder; + } boxBody.empty().append($('

').append($('').text(noDataForChart))); - //holder.remove(); } return; } @@ -268,13 +268,11 @@ function drawAChart(URI, container, chartType, options, colorData) { } if (allCharts.hasOwnProperty(container)) { - console.log('Will draw updated ' + chartType + ' chart'); allCharts[container].data.datasets = data.datasets; allCharts[container].data.labels = data.labels; allCharts[container].update(); } else { // new chart! - console.log('Will draw new ' + chartType + 'chart'); var ctx = document.getElementById(container).getContext("2d"); allCharts[container] = new Chart(ctx, { type: chartType, @@ -284,8 +282,6 @@ function drawAChart(URI, container, chartType, options, colorData) { } }).fail(function () { - console.log('Failed to draw ' + chartType + ' in container ' + container); $('#' + container).addClass('general-chart-error'); }); - console.log('URL for ' + chartType + ' chart : ' + URI); } diff --git a/public/js/ff/export/index.js b/public/js/ff/export/index.js index eeed44981f..006afca9ba 100644 --- a/public/js/ff/export/index.js +++ b/public/js/ff/export/index.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: jobKey */ + var intervalId = 0; $(function () { @@ -24,7 +26,6 @@ $(function () { function startExport() { "use strict"; - console.log('Start export...'); hideForm(); showLoading(); hideError(); @@ -75,15 +76,12 @@ function showError(text) { function callExport() { "use strict"; - console.log('Start callExport()...') var data = $('#export').serialize(); // call status, keep calling it until response is "finished"? intervalId = window.setInterval(checkStatus, 500); - $.post('export/submit', data).done(function (data) { - console.log('Export hath succeeded!'); - + $.post('export/submit', data).done(function () { // stop polling: window.clearTimeout(intervalId); @@ -114,7 +112,6 @@ function callExport() { function checkStatus() { "use strict"; - console.log('get status...'); $.getJSON('export/status/' + jobKey).done(function (data) { putStatusText(data.status); }); diff --git a/public/js/ff/firefly.js b/public/js/ff/firefly.js index 2b105d4386..82a22f16c1 100644 --- a/public/js/ff/firefly.js +++ b/public/js/ff/firefly.js @@ -7,10 +7,18 @@ * * See the LICENSE file for details. */ +/** global: moment, dateRangeConfig, accounting, currencySymbol, mon_decimal_point, frac_digits, showFullList, showOnlyTop, mon_thousands_sep */ + $(function () { "use strict"; + $.ajaxSetup({ + headers: { + 'X-CSRF-Token': $('meta[name="_token"]').attr('content') + } + }); + // when you click on a currency, this happens: $('.currency-option').click(currencySelect); @@ -49,18 +57,12 @@ $(function () { $.post(dateRangeConfig.URL, { start: start.format('YYYY-MM-DD'), end: end.format('YYYY-MM-DD'), - label: label, - _token: token + label: label }).done(function () { - console.log('Succesfully sent new date range [' + start.format('YYYY-MM-DD') + '-' + end.format('YYYY-MM-DD') + '].'); window.location.reload(true); }).fail(function () { - console.log('Could not send new date range.'); alert('Could not change date range'); - }); - - //alert('A date range was chosen: ' + start.format('YYYY-MM-DD') + ' to ' + end.format('YYYY-MM-DD')); } ); @@ -133,7 +135,6 @@ function triggerList(e) { "use strict"; var link = $(e.target); var table = link.parent().parent().parent().parent(); - console.log('data-hidden = ' + table.attr('data-hidden')); if (table.attr('data-hidden') === 'no') { // hide all elements, return false. table.find('.overListLength').hide(); diff --git a/public/js/ff/import/status.js b/public/js/ff/import/status.js index 0b24c0c7f5..cf97ab9935 100644 --- a/public/js/ff/import/status.js +++ b/public/js/ff/import/status.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: jobImportUrl, langImportSingleError, langImportMultiError, jobStartUrl, langImportTimeOutError, langImportFinished, langImportFatalError */ + var startedImport = false; var startInterval = 2000; var interval = 500; @@ -28,20 +30,17 @@ $(function () { function checkImportStatus() { "use strict"; - console.log('checkImportStatus()'); $.getJSON(jobImportUrl).done(reportOnJobImport).fail(failedJobImport); } -function importComplete(data) { +function importComplete() { "use strict"; - console.log('importComplete()'); var bar = $('#import-status-bar'); bar.removeClass('active'); } function updateBar(data) { "use strict"; - console.log('updateBar()'); var bar = $('#import-status-bar'); if (data.showPercentage) { bar.addClass('progress-bar-success').removeClass('progress-bar-info'); @@ -50,7 +49,7 @@ function updateBar(data) { $('#import-status-bar').text(data.stepsDone + '/' + data.steps); if (data.percentage >= 100) { - importComplete(data); + importComplete(); return; } return; @@ -63,7 +62,6 @@ function updateBar(data) { function reportErrors(data) { "use strict"; - console.log('reportErrors()'); if (data.errors.length == 1) { $('#import-status-error-intro').text(langImportSingleError); //'An error has occured during the import. The import can continue, however.' @@ -83,21 +81,18 @@ function reportErrors(data) { function reportStatus(data) { "use strict"; - console.log('reportStatus()'); $('#import-status-txt').removeClass('text-danger').text(data.statusText); } function kickStartJob() { "use strict"; - console.log('kickStartJob()'); - $.post(jobStartUrl, {_token: token}); + $.post(jobStartUrl); startedTheImport(); startedImport = true; } function updateTimeout(data) { "use strict"; - console.log('updateTimeout()'); if (data.stepsDone != stepCount) { stepCount = data.stepsDone; currentLimit = 0; @@ -105,12 +100,10 @@ function updateTimeout(data) { } currentLimit = currentLimit + interval; - // console.log("stepCount: " + stepCount + ", stepsDone: " + data.stepsDone + ", currentLimit: " + currentLimit); } function timeoutError() { "use strict"; - console.log('timeoutError()'); // set status $('#import-status-txt').addClass('text-danger').text(langImportTimeOutError); @@ -121,13 +114,11 @@ function timeoutError() { function importJobFinished(data) { "use strict"; - console.log('importJobFinished() = ' + data.finished); return data.finished; } function finishedJob(data) { "use strict"; - console.log('finishedJob()'); // "There was an error during the import routine. Please check the log files. The error seems to be: '" $('#import-status-txt').removeClass('text-danger').addClass('text-success').text(langImportFinished); @@ -142,7 +133,6 @@ function finishedJob(data) { function reportOnJobImport(data) { "use strict"; - console.log('reportOnJobImport()'); updateBar(data); reportErrors(data); reportStatus(data); @@ -173,13 +163,11 @@ function reportOnJobImport(data) { function startedTheImport() { "use strict"; - console.log('startedTheImport()'); setTimeout(checkImportStatus, interval); } function failedJobImport(jqxhr, textStatus, error) { "use strict"; - console.log('failedJobImport()'); // set status // "There was an error during the import routine. Please check the log files. The error seems to be: '" $('#import-status-txt').addClass('text-danger').text(langImportFatalError + ' ' + textStatus + ' ' + error); diff --git a/public/js/ff/index.js b/public/js/ff/index.js index 27d69d5bc0..ba6c9993e1 100644 --- a/public/js/ff/index.js +++ b/public/js/ff/index.js @@ -8,11 +8,13 @@ * See the LICENSE file for details. */ +/** global: Tour, showTour, accountFrontpageUri, billCount, accountExpenseUri, accountRevenueUri */ + $(function () { "use strict"; // do chart JS stuff. drawChart(); - if (showTour) { + if (showTour == true) { $.getJSON('json/tour').done(function (data) { var tour = new Tour( { @@ -24,8 +26,6 @@ $(function () { tour.init(); // Start the tour tour.start(); - }).fail(function () { - console.log('Already had tour.'); }); } @@ -34,7 +34,7 @@ $(function () { function endTheTour() { "use strict"; - $.post('json/end-tour', {_token: token}); + $.post('json/end-tour'); } @@ -57,6 +57,9 @@ function getBoxAmounts() { "use strict"; var boxes = ['in', 'out', 'bills-unpaid', 'bills-paid']; for (var x in boxes) { + if (!boxes.hasOwnProperty(x)) { + continue; + } var box = boxes[x]; $.getJSON('json/box/' + box).done(putData).fail(failData); } @@ -69,5 +72,4 @@ function putData(data) { function failData() { "use strict"; - console.log('Failed to get box!'); } \ No newline at end of file diff --git a/public/js/ff/piggy-banks/index.js b/public/js/ff/piggy-banks/index.js index 92b64b0af9..70630ad6d5 100644 --- a/public/js/ff/piggy-banks/index.js +++ b/public/js/ff/piggy-banks/index.js @@ -24,7 +24,7 @@ $(function () { $('.addMoney').on('click', addMoney); $('.removeMoney').on('click', removeMoney); - $('#sortable tbody').sortable( + $('#sortable-piggy tbody').sortable( { helper: fixHelper, stop: stopSorting, @@ -73,12 +73,12 @@ function stopSorting() { "use strict"; $('.loadSpin').addClass('fa fa-refresh fa-spin'); var order = []; - $.each($('#sortable>tbody>tr'), function (i, v) { + $.each($('#sortable-piggy>tbody>tr'), function (i, v) { var holder = $(v); var id = holder.data('id'); order.push(id); }); - $.post('piggy-banks/sort', {_token: token, order: order}).done(function () { + $.post('piggy-banks/sort', {order: order}).done(function () { $('.loadSpin').removeClass('fa fa-refresh fa-spin'); }); } \ No newline at end of file diff --git a/public/js/ff/piggy-banks/show.js b/public/js/ff/piggy-banks/show.js index 4c02df137c..be5093f6ba 100644 --- a/public/js/ff/piggy-banks/show.js +++ b/public/js/ff/piggy-banks/show.js @@ -7,6 +7,7 @@ * * See the LICENSE file for details. */ +/** global: piggyBankID, lineChart */ $(function () { "use strict"; diff --git a/public/js/ff/reports/audit/all.js b/public/js/ff/reports/audit/all.js index 2c58e0ac54..1724789bf4 100644 --- a/public/js/ff/reports/audit/all.js +++ b/public/js/ff/reports/audit/all.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: hideable */ + $(function () { "use strict"; @@ -18,7 +20,6 @@ $(function () { arr.forEach(function (val) { $('input[type="checkbox"][value="' + val + '"]').prop('checked', true); }); - console.log('arr from cookie is ' + arr) } else { // no cookie? read list, store in array 'arr' // all account ids: @@ -45,7 +46,6 @@ function clickColumnOption() { function storeCheckboxes(checkboxes) { "use strict"; // store new cookie with those options: - console.log('Store new cookie with those options: ' + checkboxes); createCookie('audit-option-checkbox', checkboxes, 365); } @@ -59,7 +59,6 @@ function readCheckboxes() { checkboxes.push(c.val()); } }); - console.log('arr is now (default): ' + checkboxes); return checkboxes; } @@ -68,12 +67,10 @@ function showOnlyColumns(checkboxes) { for (var i = 0; i < hideable.length; i++) { var opt = hideable[i]; - if(checkboxes.indexOf(opt) > -1) { - console.log(opt + ' is in checkboxes'); + if (checkboxes.indexOf(opt) > -1) { $('td.hide-' + opt).show(); $('th.hide-' + opt).show(); } else { - console.log(opt + ' is NOT in checkboxes'); $('th.hide-' + opt).hide(); $('td.hide-' + opt).hide(); } @@ -101,8 +98,12 @@ function readCookie(name) { var ca = document.cookie.split(';'); for (var i = 0; i < ca.length; i++) { var c = ca[i]; - while (c.charAt(0) === ' ') c = c.substring(1, c.length); - if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length)); + while (c.charAt(0) === ' ') { + c = c.substring(1, c.length); + } + if (c.indexOf(nameEQ) === 0) { + return decodeURIComponent(c.substring(nameEQ.length, c.length)); + } } return null; } diff --git a/public/js/ff/reports/budget/month.js b/public/js/ff/reports/budget/month.js index c103e00dff..14d845fb6e 100644 --- a/public/js/ff/reports/budget/month.js +++ b/public/js/ff/reports/budget/month.js @@ -8,6 +8,7 @@ * See the LICENSE file for details. */ +/** global: budgetExpenseUri, accountExpenseUri, mainUri */ $(function () { "use strict"; @@ -47,7 +48,6 @@ function redrawPieChart(container, uri) { others = '1'; } uri = uri.replace('OTHERS', others); - console.log('URI for ' + container + ' is ' + uri); pieChart(uri, container); diff --git a/public/js/ff/reports/category/month.js b/public/js/ff/reports/category/month.js index 607d635205..84f3444d9b 100644 --- a/public/js/ff/reports/category/month.js +++ b/public/js/ff/reports/category/month.js @@ -8,6 +8,7 @@ * See the LICENSE file for details. */ +/** global: categoryIncomeUri, categoryExpenseUri, accountIncomeUri, accountExpenseUri, mainUri */ $(function () { "use strict"; @@ -57,7 +58,6 @@ function redrawPieChart(container, uri) { others = '1'; } uri = uri.replace('OTHERS', others); - console.log('URI for ' + container + ' is ' + uri); pieChart(uri, container); diff --git a/public/js/ff/reports/default/all.js b/public/js/ff/reports/default/all.js index b5e4bf970e..06e374dcee 100644 --- a/public/js/ff/reports/default/all.js +++ b/public/js/ff/reports/default/all.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: accountReportUri, incomeReportUri, expenseReportUri, incExpReportUri, startDate, endDate, accountIds */ + $(function () { "use strict"; @@ -45,7 +47,7 @@ function clickInfoButton(e) { $.getJSON('popup/general', {attributes: attributes}).done(respondInfoButton).fail(errorInfoButton); } -function errorInfoButton(data) { +function errorInfoButton() { "use strict"; // remove wait cursor $('body').removeClass('waiting'); @@ -62,7 +64,6 @@ function respondInfoButton(data) { function loadAjaxPartial(holder, uri) { "use strict"; - console.log('Going to grab URI ' + uri); $.get(uri).done(function (data) { displayAjaxPartial(data, holder); }).fail(function () { @@ -72,7 +73,6 @@ function loadAjaxPartial(holder, uri) { function displayAjaxPartial(data, holder) { "use strict"; - console.log('Display stuff in ' + holder); var obj = $('#' + holder); obj.removeClass('loading').html(data); @@ -98,7 +98,6 @@ function displayAjaxPartial(data, holder) { function failAjaxPartial(uri, holder) { "use strict"; - console.log('Failed to load: ' + uri); $('#' + holder).removeClass('loading').addClass('general-chart-error'); } diff --git a/public/js/ff/reports/default/month.js b/public/js/ff/reports/default/month.js index 2052997a9b..1c2f08be63 100644 --- a/public/js/ff/reports/default/month.js +++ b/public/js/ff/reports/default/month.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: categoryReportUri, budgetReportUri, balanceReportUri, accountChartUri */ + $(function () { "use strict"; drawChart(); diff --git a/public/js/ff/reports/default/multi-year.js b/public/js/ff/reports/default/multi-year.js index a6a63a1d3d..1bd49a294d 100644 --- a/public/js/ff/reports/default/multi-year.js +++ b/public/js/ff/reports/default/multi-year.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: budgetPeriodReportUri, categoryExpenseUri, categoryIncomeUri, netWorthUri, opChartUri, sumChartUri */ + $(function () { "use strict"; drawChart(); diff --git a/public/js/ff/reports/default/year.js b/public/js/ff/reports/default/year.js index 9e521ab4fb..c1eb1070f3 100644 --- a/public/js/ff/reports/default/year.js +++ b/public/js/ff/reports/default/year.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: budgetPeriodReportUri, categoryExpenseUri, categoryIncomeUri, netWorthUri, opChartUri, sumChartUri */ + $(function () { "use strict"; drawChart(); diff --git a/public/js/ff/reports/index.js b/public/js/ff/reports/index.js index f146d78bf3..d59c0b3d30 100644 --- a/public/js/ff/reports/index.js +++ b/public/js/ff/reports/index.js @@ -8,12 +8,14 @@ * See the LICENSE file for details. */ +/** global: minDate */ + $(function () { "use strict"; if ($('#inputDateRange').length > 0) { - picker = $('#inputDateRange').daterangepicker( + $('#inputDateRange').daterangepicker( { locale: { format: 'YYYY-MM-DD', @@ -26,7 +28,6 @@ $(function () { // set values from cookies, if any: if (!(readCookie('report-type') === null)) { - console.log(readCookie('report-type')); $('select[name="report_type"]').val(readCookie('report-type')); } @@ -61,7 +62,6 @@ function getReportOptions() { var reportType = $('select[name="report_type"]').val(); $('#extra-options').empty(); $('#extra-options').addClass('loading'); - console.log('Changed report type to ' + reportType); $.getJSON('reports/options/' + reportType, function (data) { $('#extra-options').removeClass('loading').html(data.html); @@ -170,8 +170,12 @@ function readCookie(name) { var ca = document.cookie.split(';'); for (var i = 0; i < ca.length; i++) { var c = ca[i]; - while (c.charAt(0) === ' ') c = c.substring(1, c.length); - if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length)); + while (c.charAt(0) === ' ') { + c = c.substring(1, c.length); + } + if (c.indexOf(nameEQ) === 0) { + return decodeURIComponent(c.substring(nameEQ.length, c.length)); + } } return null; } diff --git a/public/js/ff/rules/create-edit.js b/public/js/ff/rules/create-edit.js index 98b77e9b4d..cd36a7b1ad 100644 --- a/public/js/ff/rules/create-edit.js +++ b/public/js/ff/rules/create-edit.js @@ -11,12 +11,6 @@ var triggerCount = 0; var actionCount = 0; -$(function () { - "use strict"; - console.log('edit-create'); -}); - - function addNewTrigger() { "use strict"; triggerCount++; @@ -41,7 +35,6 @@ function addNewAction() { actionCount++; $.getJSON('json/action', {count: actionCount}).done(function (data) { - //console.log(data.html); $('tbody.rule-action-tbody').append(data.html); // add action things. @@ -59,14 +52,14 @@ function addNewAction() { function removeTrigger(e) { "use strict"; var target = $(e.target); - if(target.prop("tagName") == "I") { + if (target.prop("tagName") == "I") { target = target.parent(); } // remove grand parent: target.parent().parent().remove(); // if now at zero, immediatly add one again: - if($('.rule-trigger-tbody tr').length == 0) { + if ($('.rule-trigger-tbody tr').length == 0) { addNewTrigger(); } } @@ -74,42 +67,41 @@ function removeTrigger(e) { function removeAction(e) { "use strict"; var target = $(e.target); - if(target.prop("tagName") == "I") { + if (target.prop("tagName") == "I") { target = target.parent(); } // remove grand parent: target.parent().parent().remove(); // if now at zero, immediatly add one again: - if($('.rule-action-tbody tr').length == 0) { + if ($('.rule-action-tbody tr').length == 0) { addNewAction(); } } function testRuleTriggers() { - "use strict"; - - // Serialize all trigger data - var triggerData = $( ".rule-trigger-tbody" ).find( "input[type=text], input[type=checkbox], select" ).serializeArray(); - - // Find a list of existing transactions that match these triggers + "use strict"; + + // Serialize all trigger data + var triggerData = $(".rule-trigger-tbody").find("input[type=text], input[type=checkbox], select").serializeArray(); + + // Find a list of existing transactions that match these triggers $.get('rules/test', triggerData).done(function (data) { - var modal = $( "#testTriggerModal" ); - var numTriggers = $( ".rule-trigger-body > tr" ).length; - - // Set title and body - modal.find( ".transactions-list" ).html(data.html); - - // Show warning if appropriate - if( data.warning ) { - modal.find( ".transaction-warning .warning-contents" ).text(data.warning); - modal.find( ".transaction-warning" ).show(); - } else { - modal.find( ".transaction-warning" ).hide(); - } - - // Show the modal dialog - $( "#testTriggerModal" ).modal(); + var modal = $("#testTriggerModal"); + + // Set title and body + modal.find(".transactions-list").html(data.html); + + // Show warning if appropriate + if (data.warning) { + modal.find(".transaction-warning .warning-contents").text(data.warning); + modal.find(".transaction-warning").show(); + } else { + modal.find(".transaction-warning").hide(); + } + + // Show the modal dialog + $("#testTriggerModal").modal(); }).fail(function () { alert('Cannot get transactions for given triggers.'); }); diff --git a/public/js/ff/rules/create.js b/public/js/ff/rules/create.js index 1ad133f9d7..ffa65be20e 100644 --- a/public/js/ff/rules/create.js +++ b/public/js/ff/rules/create.js @@ -8,9 +8,11 @@ * See the LICENSE file for details. */ +/** global: triggerCount, actionCount */ + + $(function () { "use strict"; - console.log("create"); if (triggerCount === 0) { addNewTrigger(); } diff --git a/public/js/ff/rules/edit.js b/public/js/ff/rules/edit.js index 16191e2ae8..893feff138 100644 --- a/public/js/ff/rules/edit.js +++ b/public/js/ff/rules/edit.js @@ -8,10 +8,10 @@ * See the LICENSE file for details. */ +/** global: triggerCount, actionCount */ + $(function () { "use strict"; - console.log("edit"); - if (triggerCount === 0) { addNewTrigger(); } diff --git a/public/js/ff/rules/index.js b/public/js/ff/rules/index.js index c8642209e2..0fc0ef7371 100644 --- a/public/js/ff/rules/index.js +++ b/public/js/ff/rules/index.js @@ -48,21 +48,19 @@ function sortStop(event, ui) { var ruleId = current.parent().data('id'); var entries = []; // who am i? - console.log('Rule: #' + current.parent().data('id')); $.each(parent.children(), function (i, v) { var trigger = $(v); var id = trigger.data('id'); - var order = i + 1; entries.push(id); }); if (parent.hasClass('rule-triggers')) { - $.post('rules/trigger/order/' + ruleId, {_token: token, triggers: entries}).fail(function () { + $.post('rules/trigger/order/' + ruleId, {triggers: entries}).fail(function () { alert('Could not re-order rule triggers. Please refresh the page.'); }); } else { - $.post('rules/action/order/' + ruleId, {_token: token, actions: entries}).fail(function () { + $.post('rules/action/order/' + ruleId, {actions: entries}).fail(function () { alert('Could not re-order rule actions. Please refresh the page.'); }); diff --git a/public/js/ff/split/journal/from-store.js b/public/js/ff/split/journal/from-store.js index ef3480f827..a7323a12a6 100644 --- a/public/js/ff/split/journal/from-store.js +++ b/public/js/ff/split/journal/from-store.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: originalSum, accounting */ + var destAccounts = {}; var srcAccounts = {}; var categories = {}; @@ -18,19 +20,16 @@ $(function () { $.getJSON('json/expense-accounts').done(function (data) { destAccounts = data; - console.log('destAccounts length is now ' + destAccounts.length); $('input[name$="destination_account_name]"]').typeahead({source: destAccounts}); }); $.getJSON('json/revenue-accounts').done(function (data) { srcAccounts = data; - console.log('srcAccounts length is now ' + srcAccounts.length); $('input[name$="source_account_name]"]').typeahead({source: srcAccounts}); }); $.getJSON('json/categories').done(function (data) { categories = data; - console.log('categories length is now ' + categories.length); $('input[name$="category]"]').typeahead({source: categories}); }); @@ -45,12 +44,10 @@ function removeRow(e) { "use strict"; var rows = $('table.split-table tbody tr'); if (rows.length === 1) { - console.log('Will not remove last split'); return false; } var row = $(e.target); var index = row.data('split'); - console.log('Trying to remove row with split ' + index); $('table.split-table tbody tr[data-split="' + index + '"]').remove(); @@ -64,22 +61,18 @@ function cloneRow() { "use strict"; var source = $('.table.split-table tbody tr').last().clone(); var count = $('.split-table tbody tr').length + 1; - var index = count - 1; source.removeClass('initial-row'); source.find('.count').text('#' + count); source.find('input[name$="][amount]"]').val("").on('input', calculateSum); if (destAccounts.length > 0) { - console.log('Will be able to extend dest-accounts.'); source.find('input[name$="destination_account_name]"]').typeahead({source: destAccounts}); } if (destAccounts.length > 0) { - console.log('Will be able to extend src-accounts.'); source.find('input[name$="source_account_name]"]').typeahead({source: srcAccounts}); } if (categories.length > 0) { - console.log('Will be able to extend categories.'); source.find('input[name$="category]"]').typeahead({source: categories}); } @@ -103,7 +96,6 @@ function resetSplits() { $.each($('table.split-table tbody tr'), function (i, v) { var row = $(v); row.attr('data-split', i); - console.log('Row is now ' + row.data('split')); }); // loop each remove button, update the index @@ -111,7 +103,6 @@ function resetSplits() { var button = $(v); button.attr('data-split', i); button.find('i').attr('data-split', i); - console.log('Remove button index is now ' + button.data('split')); }); @@ -120,7 +111,6 @@ function resetSplits() { var cell = $(v); var index = i + 1; cell.text('#' + index); - console.log('Cell is now ' + cell.text()); }); // loop each possible field. @@ -129,37 +119,31 @@ function resetSplits() { $.each($('input[name$="][description]"]'), function (i, v) { var input = $(v); input.attr('name', 'transactions[' + i + '][description]'); - console.log('description is now ' + input.attr('name')); }); // ends with ][destination_account_name] $.each($('input[name$="][destination_account_name]"]'), function (i, v) { var input = $(v); input.attr('name', 'transactions[' + i + '][destination_account_name]'); - console.log('destination_account_name is now ' + input.attr('name')); }); // ends with ][source_account_name] $.each($('input[name$="][source_account_name]"]'), function (i, v) { var input = $(v); input.attr('name', 'transactions[' + i + '][source_account_name]'); - console.log('source_account_name is now ' + input.attr('name')); }); // ends with ][amount] $.each($('input[name$="][amount]"]'), function (i, v) { var input = $(v); input.attr('name', 'transactions[' + i + '][amount]'); - console.log('amount is now ' + input.attr('name')); }); // ends with ][budget_id] $.each($('select[name$="][budget_id]"]'), function (i, v) { var input = $(v); input.attr('name', 'transactions[' + i + '][budget_id]'); - console.log('budget_id is now ' + input.attr('name')); }); // ends with ][category] $.each($('input[name$="][category]"]'), function (i, v) { var input = $(v); input.attr('name', 'transactions[' + i + '][category]'); - console.log('category is now ' + input.attr('name')); }); } @@ -173,12 +157,10 @@ function calculateSum() { } sum = Math.round(sum * 100) / 100; - console.log("Sum is now " + sum); $('.amount-warning').remove(); if (sum != originalSum) { - console.log(sum + ' does not match ' + originalSum); var holder = $('#journal_amount_holder'); var par = holder.find('p.form-control-static'); - var amount = $('').text(' (' + accounting.formatMoney(sum) + ')').addClass('text-danger amount-warning').appendTo(par); + $('').text(' (' + accounting.formatMoney(sum) + ')').addClass('text-danger amount-warning').appendTo(par); } } \ No newline at end of file diff --git a/public/js/ff/tags/create.js b/public/js/ff/tags/create-edit.js similarity index 86% rename from public/js/ff/tags/create.js rename to public/js/ff/tags/create-edit.js index 77e8224238..397f49ccef 100644 --- a/public/js/ff/tags/create.js +++ b/public/js/ff/tags/create-edit.js @@ -1,12 +1,11 @@ /* - * create.js - * Copyright (C) 2016 thegrumpydictator@gmail.com - * - * This software may be modified and distributed under the terms of the - * Creative Commons Attribution-ShareAlike 4.0 International License. + * create-edit.js + * Copyright (c) 2017 thegrumpydictator@gmail.com + * This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License. * * See the LICENSE file for details. */ +/** global: zoomLevel, latitude, longitude, google, apiKey, doPlaceMarker */ $(function () { "use strict"; @@ -60,12 +59,12 @@ function initialize() { Respond to zoom event. */ google.maps.event.addListener(map, 'zoom_changed', function () { - saveZoomLevel(event); + saveZoomLevel(); }); /* Maybe place marker? */ - if(doPlaceMarker) { + if(doPlaceMarker == true) { var myLatlng = new google.maps.LatLng(latitude,longitude); var fakeEvent = {}; fakeEvent.latLng = myLatlng; diff --git a/public/js/ff/tags/edit.js b/public/js/ff/tags/edit.js deleted file mode 100644 index c4f9cd0cfb..0000000000 --- a/public/js/ff/tags/edit.js +++ /dev/null @@ -1,113 +0,0 @@ -/* - * edit.js - * Copyright (C) 2016 thegrumpydictator@gmail.com - * - * This software may be modified and distributed under the terms of the - * Creative Commons Attribution-ShareAlike 4.0 International License. - * - * See the LICENSE file for details. - */ - -$(function () { - "use strict"; - - $('#clearLocation').click(clearLocation); - -}); - -/* - Some vars as prep for the map: - */ -var map; -var markers = []; -var setTag = false; - -var mapOptions = { - zoom: zoomLevel, - center: new google.maps.LatLng(latitude, longitude), - disableDefaultUI: true -}; - -/* - Clear location and reset zoomLevel. - */ -function clearLocation() { - "use strict"; - deleteMarkers(); - $('input[name="latitude"]').val(""); - $('input[name="longitude"]').val(""); - $('input[name="zoomLevel"]').val("6"); - setTag = false; - $('input[name="setTag"]').val('false'); - return false; -} - -function initialize() { - "use strict"; - /* - Create new map: - */ - map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions); - - /* - Respond to click event. - */ - google.maps.event.addListener(map, 'rightclick', function (event) { - placeMarker(event); - }); - - /* - Respond to zoom event. - */ - google.maps.event.addListener(map, 'zoom_changed', function () { - saveZoomLevel(event); - }); - /* - Maybe place marker? - */ - if(doPlaceMarker) { - var myLatlng = new google.maps.LatLng(latitude,longitude); - var fakeEvent = {}; - fakeEvent.latLng = myLatlng; - placeMarker(fakeEvent); - - } -} - -/** - * save zoom level of map into hidden input. - */ -function saveZoomLevel() { - "use strict"; - $('input[name="zoomLevel"]').val(map.getZoom()); -} - -/** - * Place marker on map. - * @param event - */ -function placeMarker(event) { - "use strict"; - deleteMarkers(); - var marker = new google.maps.Marker({position: event.latLng, map: map}); - $('input[name="latitude"]').val(event.latLng.lat()); - $('input[name="longitude"]').val(event.latLng.lng()); - markers.push(marker); - setTag = true; - $('input[name="setTag"]').val('true'); -} - - -/** - * Deletes all markers in the array by removing references to them. - */ -function deleteMarkers() { - "use strict"; - for (var i = 0; i < markers.length; i++) { - markers[i].setMap(null); - } - markers = []; -} - - -google.maps.event.addDomListener(window, 'load', initialize); \ No newline at end of file diff --git a/public/js/ff/tags/index.js b/public/js/ff/tags/index.js index a24180b4e0..990b265895 100644 --- a/public/js/ff/tags/index.js +++ b/public/js/ff/tags/index.js @@ -8,106 +8,8 @@ * See the LICENSE file for details. */ +/** global: zoomLevel, latitude, longitude, google */ + $(function () { "use strict"; - - $('#clearLocation').click(clearLocation); - }); - -/* - Some vars as prep for the map: - */ -var map; -var markers = []; -var setTag = false; - -var mapOptions = { - zoom: zoomLevel, - center: new google.maps.LatLng(latitude, longitude), - disableDefaultUI: true -}; - -/* - Clear location and reset zoomLevel. - */ -function clearLocation() { - "use strict"; - deleteMarkers(); - $('input[name="latitude"]').val(""); - $('input[name="longitude"]').val(""); - $('input[name="zoomLevel"]').val("6"); - setTag = false; - $('input[name="setTag"]').val('false'); - return false; -} - -function initialize() { - "use strict"; - /* - Create new map: - */ - map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions); - - /* - Respond to click event. - */ - google.maps.event.addListener(map, 'rightclick', function (event) { - placeMarker(event); - }); - - /* - Respond to zoom event. - */ - google.maps.event.addListener(map, 'zoom_changed', function () { - saveZoomLevel(event); - }); - /* - Maybe place marker? - */ - if(doPlaceMarker) { - var myLatlng = new google.maps.LatLng(latitude,longitude); - var fakeEvent = {}; - fakeEvent.latLng = myLatlng; - placeMarker(fakeEvent); - - } -} - -/** - * save zoom level of map into hidden input. - */ -function saveZoomLevel() { - "use strict"; - $('input[name="zoomLevel"]').val(map.getZoom()); -} - -/** - * Place marker on map. - * @param event - */ -function placeMarker(event) { - "use strict"; - deleteMarkers(); - var marker = new google.maps.Marker({position: event.latLng, map: map}); - $('input[name="latitude"]').val(event.latLng.lat()); - $('input[name="longitude"]').val(event.latLng.lng()); - markers.push(marker); - setTag = true; - $('input[name="setTag"]').val('true'); -} - - -/** - * Deletes all markers in the array by removing references to them. - */ -function deleteMarkers() { - "use strict"; - for (var i = 0; i < markers.length; i++) { - markers[i].setMap(null); - } - markers = []; -} - - -google.maps.event.addDomListener(window, 'load', initialize); \ No newline at end of file diff --git a/public/js/ff/transactions/create-edit.js b/public/js/ff/transactions/create-edit.js index ae827d8983..9719b76d6b 100644 --- a/public/js/ff/transactions/create-edit.js +++ b/public/js/ff/transactions/create-edit.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: what */ + $(document).ready(function () { "use strict"; @@ -58,19 +60,19 @@ $(document).ready(function () { } - if ($('input[name="description"]').length > 0 && what !== undefined) { + if ($('input[name="description"]').length > 0 && !(typeof what === "undefined")) { $.getJSON('json/transaction-journals/' + what).done(function (data) { $('input[name="description"]').typeahead({source: data}); }); } // also for multi input: - if ($('input[name="description[]"]').length > 0 && what !== undefined) { + if ($('input[name="description[]"]').length > 0 && !(typeof what === "undefined")) { $.getJSON('json/transaction-journals/' + what).done(function (data) { $('input[name="description[]"]').typeahead({source: data}); }); } // and for the (rare) journal_description: - if ($('input[name="journal_description"]').length > 0 && what !== undefined) { + if ($('input[name="journal_description"]').length > 0 && !(typeof what === "undefined")) { $.getJSON('json/transaction-journals/' + what).done(function (data) { $('input[name="journal_description"]').typeahead({source: data}); }); diff --git a/public/js/ff/transactions/create.js b/public/js/ff/transactions/create.js index e059fdf127..aaed65207a 100644 --- a/public/js/ff/transactions/create.js +++ b/public/js/ff/transactions/create.js @@ -8,12 +8,14 @@ * See the LICENSE file for details. */ +/** global: what, title, breadcrumbs, middleCrumbName, button, piggiesLength, txt, doSwitch, middleCrumbUrl */ + $(document).ready(function () { "use strict"; // respond to switch buttons when // creating stuff: - if (doSwitch) { + if (doSwitch == true) { updateButtons(); updateForm(); updateLayout(); @@ -95,6 +97,9 @@ function updateForm() { $('#piggy_bank_id_holder').show(); } break; + default: + // no action. + break; } } @@ -111,7 +116,6 @@ function updateButtons() { if (button.data('what') == what) { button.removeClass('btn-default').addClass('btn-info').html(' ' + txt[button.data('what')]); - console.log('Now displaying form for ' + what); } else { button.removeClass('btn-info').addClass('btn-default').text(txt[button.data('what')]); } diff --git a/public/js/ff/transactions/list.js b/public/js/ff/transactions/list.js index 7e87446a48..74dc53f24d 100644 --- a/public/js/ff/transactions/list.js +++ b/public/js/ff/transactions/list.js @@ -8,6 +8,8 @@ * See the LICENSE file for details. */ +/** global: edit_selected_txt, delete_selected_txt */ + $(document).ready(function () { "use strict"; $('.mass_edit_all').show(); diff --git a/public/js/lib/jquery-ui.min.js b/public/js/lib/jquery-ui.min.js index 25398a1674..cf8c80b490 100644 --- a/public/js/lib/jquery-ui.min.js +++ b/public/js/lib/jquery-ui.min.js @@ -1,13 +1,7 @@ -/*! jQuery UI - v1.12.1 - 2016-09-14 +/*! jQuery UI - v1.12.1 - 2017-01-01 * http://jqueryui.com -* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js +* Includes: widget.js, data.js, scroll-parent.js, widgets/sortable.js, widgets/mouse.js * Copyright jQuery Foundation and other contributors; Licensed MIT */ -(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){function e(t){for(var e=t.css("visibility");"inherit"===e;)t=t.parent(),e=t.css("visibility");return"hidden"!==e}function i(t){for(var e,i;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(i=parseInt(t.css("zIndex"),10),!isNaN(i)&&0!==i))return i;t=t.parent()}return 0}function s(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=n(t("

"))}function n(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.on("mouseout",i,function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).on("mouseover",i,o)}function o(){t.datepicker._isDisabledDatepicker(m.inline?m.dpDiv.parent()[0]:m.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,i){t.extend(e,i);for(var s in i)null==i[s]&&(e[s]=i[s]);return e}function r(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.ui.version="1.12.1";var h=0,l=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},n=t[h][e],o=t[h][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:h,widgetName:e,widgetFullName:l}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var i,s,n=l.call(arguments,1),o=0,a=n.length;a>o;o++)for(i in n[o])s=n[o][i],n[o].hasOwnProperty(i)&&void 0!==s&&(e[i]=t.isPlainObject(s)?t.isPlainObject(e[i])?t.widget.extend({},e[i],s):t.widget.extend({},s):s);return e},t.widget.bridge=function(e,i){var s=i.prototype.widgetFullName||e;t.fn[e]=function(n){var o="string"==typeof n,a=l.call(arguments,1),r=this;return o?this.length||"instance"!==n?this.each(function(){var i,o=t.data(this,s);return"instance"===n?(r=o,!1):o?t.isFunction(o[n])&&"_"!==n.charAt(0)?(i=o[n].apply(o,a),i!==o&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+n+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+n+"'")}):r=void 0:(a.length&&(n=t.widget.extend.apply(null,[n].concat(a))),this.each(function(){var e=t.data(this,s);e?(e.option(n||{}),e._init&&e._init()):t.data(this,s,new i(n,this))})),r}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{classes:{},disabled:!1,create:null},_createWidget:function(e,i){i=t(i||this.defaultElement||this)[0],this.element=t(i),this.uuid=h++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},i!==this&&(t.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===i&&this.destroy()}}),this.document=t(i.style?i.ownerDocument:i.document||i),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+o.eventNamespace,c=h[2];c?n.on(l,c,r):i.on(l,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("
"),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widthi?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};l>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),h.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-a-n;e.collisionWidth>a?h>0&&0>=l?(i=t.left+h+e.collisionWidth-a-n,t.left+=h-i):t.left=l>0&&0>=h?n:h>l?n+a-e.collisionWidth:n:h>0?t.left+=h:l>0?t.left-=l:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,h=n-r,l=r+e.collisionHeight-a-n;e.collisionHeight>a?h>0&&0>=l?(i=t.top+h+e.collisionHeight-a-n,t.top+=h-i):t.top=l>0&&0>=h?n:h>l?n+a-e.collisionHeight:n:h>0?t.top+=h:l>0?t.top-=l:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,u=l+e.collisionWidth-r-h,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-h,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,u=l+e.collisionHeight-r-h,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-h,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}});var c="ui-effects-",u="ui-effects-style",d="ui-effects-animated",p=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,o){var a,r=o.re.exec(i),h=r&&o.parse(r),l=o.space||"rgba";return h?(a=s[l](h),s[c[l].cache]=a[c[l].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("

")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,a,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,h],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),o=c[n],a=0===this.alpha()?l("transparent"):this,r=a[o.cache]||o.to(a._rgba),h=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],l=s[o],c=u[n.type]||{};null!==l&&(null===a?h[o]=l:(c.mod&&(l-a>c.mod/2?a+=c.mod:a-l>c.mod/2&&(a-=c.mod)),h[o]=i((l-a)*e+a,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),h=Math.min(s,n,o),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-o)/l+360:n===r?60*(o-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[a]&&(this[a]=h(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[a]=d,n):l(d)},f(o,function(e,i){l.fn[e]||(l.fn[e]=function(n){var o,a=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=l(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(p),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,o,a={};for(s in i)o=i[s],e[s]!==o&&(n[s]||(t.fx.step[s]||!isNaN(parseFloat(o)))&&(a[s]=o));return a}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(p.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var h=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",l=h.children?a.find("*").addBack():a;l=l.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function e(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function i(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),n=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,o=n.exec(t)||["",0,i,s,0];return{top:parseFloat(o[1])||0,right:"auto"===o[2]?i:parseFloat(o[2]),bottom:"auto"===o[3]?s:parseFloat(o[3]),left:parseFloat(o[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(d)||e(i)}}(t.expr.filters.animated)),t.uiBackCompat!==!1&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;s>i;i++)null!==e[i]&&t.data(c+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,n=e.length;n>s;s++)null!==e[s]&&(i=t.data(c+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("

").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,n="vertical"!==i?(e||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data(u,t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data(u)||"",t.removeData(u)},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),n=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),"float":e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data(c+"placeholder",i)),e.css({position:s,left:n.left,top:n.top}),i},removePlaceholder:function(t){var e=c+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function i(e){function i(){r.removeData(d),t.effects.cleanUp(r),"hide"===s.mode&&r.hide(),a()}function a(){t.isFunction(h)&&h.call(r[0]),t.isFunction(e)&&e()}var r=t(this);s.mode=c.shift(),t.uiBackCompat===!1||o?"none"===s.mode?(r[l](),a()):n.call(r[0],s,i):(r.is(":hidden")?"hide"===l:"show"===l)?(r[l](),a()):n.call(r[0],s,a)}var s=e.apply(this,arguments),n=t.effects.effect[s.effect],o=n.mode,a=s.queue,r=a||"fx",h=s.complete,l=s.mode,c=[],u=function(e){var i=t(this),s=t.effects.mode(i,l)||o;i.data(d,!0),c.push(s),o&&("show"===s||s===o&&"hide"===s)&&i.show(),o&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!n?l?this[l](s.duration,h):this.each(function(){h&&h.call(this)}):a===!1?this.each(u).each(i):this.queue(r,u).queue(r,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="show",this.effect.call(this,n) -}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("
").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var f=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},h=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),h&&h.css(t.effects.clipToBox(r)),r.clip=a),h&&h.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,h="hide"===r,l="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(l||h?1:0),f=e.duration/p,g=e.easing,m="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u||(u=a["top"===m?"outerHeight":"outerWidth"]()/3),l&&(n={opacity:1},n[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),h&&(u/=Math.pow(2,d-1)),n={},n[m]=o;d>v;v++)s={},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g).animate(n,f,g),u=h?2*u:u/2;h&&(s={opacity:0},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("clip","hide",function(e,i){var s,n={},o=t(this),a=e.direction||"vertical",r="both"===a,h=r||"horizontal"===a,l=r||"vertical"===a;s=o.cssClip(),n.clip={top:l?(s.bottom-s.top)/2:s.top,right:h?(s.right-s.left)/2:s.right,bottom:l?(s.bottom-s.top)/2:s.bottom,left:h?(s.right-s.left)/2:s.left},t.effects.createPlaceholder(o),"show"===e.mode&&(o.cssClip(n.clip),n.clip=s),o.animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",h="up"===r||"down"===r?"top":"left",l="up"===r||"left"===r?"-=":"+=",c="+="===l?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0)/2,u[h]=l+s,a&&(n.css(u),u[h]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("explode","hide",function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=e.mode,g="show"===f,m=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*_,l=a-(d-1)/2,p.clone().appendTo("body").wrap("
").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacity:g?1:0},e.duration||500,e.easing,s)}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=e.size||15,h=/([0-9]+)%/.exec(r),l=!!e.horizFirst,c=l?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},g={clip:t.extend({},p)},m=[p[c[0]],p[c[1]]],_=s.queue().length;h&&(r=parseInt(h[1],10)/100*m[a?0:1]),f.clip[c[0]]=r,g.clip[c[0]]=r,g.clip[c[1]]=0,o&&(s.cssClip(g.clip),d&&d.css(t.effects.clipToBox(g)),g.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(g),u,e.easing),i()}).animate(f,u,e.easing).animate(g,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,n,o,a=t(this),r=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,u="effect"!==c,d=e.scale||"both",p=e.origin||["middle","center"],f=a.css("position"),g=a.position(),m=t.effects.scaledDimensions(a),_=e.from||m,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===c&&(o=_,_=v,v=o),n={from:{y:_.height/m.height,x:_.width/m.width},to:{y:v.height/m.height,x:v.width/m.width}},("box"===d||"both"===d)&&(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,h,n.from.y,_),v=t.effects.setTransition(a,h,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,l,n.from.x,_),v=t.effects.setTransition(a,l,n.to.x,v))),("content"===d||"both"===d)&&n.from.y!==n.to.y&&(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),p&&(s=t.effects.getBaseline(p,m),_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left,v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left),a.css(_),("content"===d||"both"===d)&&(h=h.concat(["marginTop","marginBottom"]).concat(r),l=l.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),o={height:s.height*n.from.y,width:s.width*n.from.x,outerHeight:s.outerHeight*n.from.y,outerWidth:s.outerWidth*n.from.x},a={height:s.height*n.to.y,width:s.width*n.to.x,outerHeight:s.height*n.to.y,outerWidth:s.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,h,n.from.y,o),a=t.effects.setTransition(i,h,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,l,n.from.x,o),a=t.effects.setTransition(i,l,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),n=e.mode,o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==n?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,o,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=o||a,h=2*(e.times||5)+(r?1:0),l=e.duration/h,c=0,u=1,d=s.queue().length;for((o||!s.is(":visible"))&&(s.css("opacity",0).show(),c=1);h>u;u++)s.animate({opacity:c},l,e.easing),c=1-c;s.animate({opacity:c},l,e.easing),s.queue(i),t.effects.unshift(s,d,h+1)}),t.effects.define("shake",function(e,i){var s=1,n=t(this),o=e.direction||"left",a=e.distance||20,r=e.times||3,h=2*r+1,l=Math.round(e.duration/h),c="up"===o||"down"===o?"top":"left",u="up"===o||"left"===o,d={},p={},f={},g=n.queue().length;for(t.effects.createPlaceholder(n),d[c]=(u?"-=":"+=")+a,p[c]=(u?"+=":"-=")+2*a,f[c]=(u?"-=":"+=")+2*a,n.animate(d,l,e.easing);r>s;s++)n.animate(p,l,e.easing).animate(f,l,e.easing);n.animate(p,l,e.easing).animate(d,l/2,e.easing).queue(i),t.effects.unshift(n,g,h+1)}),t.effects.define("slide","show",function(e,i){var s,n,o=t(this),a={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},r=e.mode,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,u=e.distance||o["top"===l?"outerHeight":"outerWidth"](!0),d={};t.effects.createPlaceholder(o),s=o.cssClip(),n=o.position()[l],d[l]=(c?-1:1)*u+n,d.clip=o.cssClip(),d.clip[a[h][1]]=d.clip[a[h][0]],"show"===r&&(o.cssClip(d.clip),o.css(l,d[l]),d.clip=s,d[l]=n),o.animate(d,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});var f;t.uiBackCompat!==!1&&(f=t.effects.define("transfer",function(e,i){t(this).transfer(e,i)})),t.ui.focusable=function(i,s){var n,o,a,r,h,l=i.nodeName.toLowerCase();return"area"===l?(n=i.parentNode,o=n.name,i.href&&o&&"map"===n.nodeName.toLowerCase()?(a=t("img[usemap='#"+o+"']"),a.length>0&&a.is(":visible")):!1):(/^(input|select|textarea|button|object)$/.test(l)?(r=!i.disabled,r&&(h=t(i).closest("fieldset")[0],h&&(r=!h.disabled))):r="a"===l?i.href||s:s,r&&t(i).is(":visible")&&e(t(i)))},t.extend(t.expr[":"],{focusable:function(e){return t.ui.focusable(e,null!=t.attr(e,"tabindex"))}}),t.ui.focusable,t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},"1.7"===t.fn.jquery.substring(0,3)&&(t.each(["Width","Height"],function(e,i){function s(e,i,s,o){return t.each(n,function(){i-=parseFloat(t.css(e,"padding"+this))||0,s&&(i-=parseFloat(t.css(e,"border"+this+"Width"))||0),o&&(i-=parseFloat(t.css(e,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],o=i.toLowerCase(),a={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+i]=function(e){return void 0===e?a["inner"+i].call(this):this.each(function(){t(this).css(o,s(this,e)+"px")})},t.fn["outer"+i]=function(e,n){return"number"!=typeof e?a["outer"+i].call(this,e):this.each(function(){t(this).css(o,s(this,e,!0,n)+"px")})}}),t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.ui.escapeSelector=function(){var t=/([!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~])/g;return function(e){return e.replace(t,"\\$1")}}(),t.fn.labels=function(){var e,i,s,n,o;return this[0].labels&&this[0].labels.length?this.pushStack(this[0].labels):(n=this.eq(0).parents("label"),s=this.attr("id"),s&&(e=this.eq(0).parents().last(),o=e.add(e.length?e.siblings():this.siblings()),i="label[for='"+t.ui.escapeSelector(s)+"']",n=n.add(o.find(i).addBack(i))),this.pushStack(n))},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.extend(t.expr[":"],{tabbable:function(e){var i=t.attr(e,"tabindex"),s=null!=i;return(!s||i>=0)&&t.ui.focusable(e,s)}}),t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.widget("ui.accordion",{version:"1.12.1",options:{active:0,animate:{},classes:{"ui-accordion-header":"ui-corner-top","ui-accordion-header-collapsed":"ui-corner-all","ui-accordion-content":"ui-corner-bottom"},collapsible:!1,event:"click",header:"> li > :first-child, > :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),0>e.active&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var e,i,s=this.options.icons;s&&(e=t(""),this._addClass(e,"ui-accordion-header-icon","ui-icon "+s.header),e.prependTo(this.headers),i=this.active.children(".ui-accordion-header-icon"),this._removeClass(i,s.header)._addClass(i,null,s.activeHeader)._addClass(this.headers,"ui-accordion-icons"))},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),void 0)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var i=t.ui.keyCode,s=this.headers.length,n=this.headers.index(e.target),o=!1;switch(e.keyCode){case i.RIGHT:case i.DOWN:o=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:o=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(e);break;case i.HOME:o=this.headers[0];break;case i.END:o=this.headers[s-1]}o&&(t(e.target).attr("tabIndex",-1),t(o).attr("tabIndex",0),t(o).trigger("focus"),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().trigger("focus")},refresh:function(){var e=this.options;this._processPanels(),e.active===!1&&e.collapsible===!0||!this.headers.length?(e.active=!1,this.active=t()):e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var e=t(this),i=e.uniqueId().attr("id"),s=e.next(),n=s.uniqueId().attr("id");e.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(e=n.height(),this.element.siblings(":visible").each(function(){var i=t(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(e-=i.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===s&&(e=0,this.headers.next().each(function(){var i=t(this).is(":visible");i||t(this).show(),e=Math.max(e,t(this).css("height","").height()),i||t(this).hide()}).height(e))},_activate:function(e){var i=this._findActive(e)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var i={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var i,s,n=this.options,o=this.active,a=t(e.currentTarget),r=a[0]===o[0],h=r&&n.collapsible,l=h?t():a.next(),c=o.next(),u={oldHeader:o,oldPanel:c,newHeader:h?t():a,newPanel:l};e.preventDefault(),r&&!n.collapsible||this._trigger("beforeActivate",e,u)===!1||(n.active=h?!1:this.headers.index(a),this.active=r?t():a,this._toggle(u),this._removeClass(o,"ui-accordion-header-active","ui-state-active"),n.icons&&(i=o.children(".ui-accordion-header-icon"),this._removeClass(i,null,n.icons.activeHeader)._addClass(i,null,n.icons.header)),r||(this._removeClass(a,"ui-accordion-header-collapsed")._addClass(a,"ui-accordion-header-active","ui-state-active"),n.icons&&(s=a.children(".ui-accordion-header-icon"),this._removeClass(s,null,n.icons.header)._addClass(s,null,n.icons.activeHeader)),this._addClass(a.next(),"ui-accordion-content-active")))},_toggle:function(e){var i=e.newPanel,s=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,e):(s.hide(),i.show(),this._toggleComplete(e)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(t(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,e,i){var s,n,o,a=this,r=0,h=t.css("box-sizing"),l=t.length&&(!e.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,h=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=h.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=h.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n; -this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t(" {% include 'emails.footer-html' %} diff --git a/resources/views/emails/registered-text.twig b/resources/views/emails/registered-text.twig index 8c2aa00143..4877bf52fd 100644 --- a/resources/views/emails/registered-text.twig +++ b/resources/views/emails/registered-text.twig @@ -14,8 +14,7 @@ Password reset: {{ address }}/password/reset Documentation: -https://github.com/JC5/firefly-iii/wiki/First-use -http://jc5.github.io/firefly-iii//description/ +https://github.com/firefly-iii/firefly-iii +https://firefly-iii.github.io/ -The registration has been created from IP {{ ip }} {% include 'emails.footer-text' %} diff --git a/resources/views/form/integer.twig b/resources/views/form/integer.twig index 9ce0e38f5b..54be537886 100644 --- a/resources/views/form/integer.twig +++ b/resources/views/form/integer.twig @@ -2,9 +2,8 @@
-
- {{ Form.input('number', name, value, options) }} - {% include 'form/feedback' %} -
+ {{ Form.input('number', name, value, options) }} + {% include 'form/help' %} + {% include 'form/feedback' %}
diff --git a/resources/views/import/complete.twig b/resources/views/import/complete.twig index 9a95c16620..932dbe2caa 100644 --- a/resources/views/import/complete.twig +++ b/resources/views/import/complete.twig @@ -15,7 +15,7 @@ {{ 'import_complete_text'|_ }}

- php artisan firefly:import {{ job.key }} + php artisan firefly:start-import {{ job.key }}

diff --git a/resources/views/layout/default.twig b/resources/views/layout/default.twig index 58c2d5c7e6..aff6f9491d 100644 --- a/resources/views/layout/default.twig +++ b/resources/views/layout/default.twig @@ -3,6 +3,7 @@ + {% if subTitle %} @@ -139,7 +140,7 @@ <div class="pull-right hidden-xs"> <b>Version</b> {{ Config.get('firefly.version') }} </div> - <strong><a href="https://github.com/JC5/firefly-iii">Firefly III</a></strong> + <strong><a href="https://github.com/firefly-iii/firefly-iii">Firefly III</a></strong> </footer> {% include('partials.control-bar') %} @@ -197,8 +198,7 @@ toLabel: '{{ 'to'|_|escape }}', ranges: {{ dpRanges|json_encode|raw }} }; - - var token = "{{ csrf_token() }}"; + var language = "{{ language|escape }}"; var currencyCode = '{{ getCurrencyCode()|escape('js') }}'; var currencySymbol = '{{ getCurrencySymbol()|escape('js') }}'; diff --git a/resources/views/list/accounts.twig b/resources/views/list/accounts.twig index 12b6111c8e..dbf3d7436c 100644 --- a/resources/views/list/accounts.twig +++ b/resources/views/list/accounts.twig @@ -10,7 +10,8 @@ <th data-defaultsign="_19">{{ trans('list.currentBalance') }}</th> <th class="hidden-sm hidden-xs">{{ trans('list.active') }}</th> <th data-defaultsign="month" class="hidden-sm hidden-xs">{{ trans('list.lastActivity') }}</th> - <th data-defaultsign="_19" class="hidden-sm hidden-xs">{{ trans('list.balanceDiff', {'start' : Session.get('start').formatLocalized(monthAndDayFormat),'end' : Session.get('end').formatLocalized(monthAndDayFormat)}) }}</th> + <th data-defaultsign="_19" + class="hidden-sm hidden-xs">{{ trans('list.balanceDiff', {'start' : Session.get('start').formatLocalized(monthAndDayFormat),'end' : Session.get('end').formatLocalized(monthAndDayFormat)}) }}</th> </tr> </thead> <tbody> @@ -33,7 +34,11 @@ </td> {% endif %} <td class="hidden-sm hidden-xs">{{ account.iban }}</td> - <td data-value="{{ account.endBalance }}">{{ account.endBalance|formatAmount }}</td> + <td data-value="{{ account.endBalance }}" style="text-align: right;"> + <span style="margin-right:5px;"> + {{ account.endBalance|formatAmount }} + </span> + </td> <td class="hidden-sm hidden-xs" data-value="{{ account.active }}"> {% if account.active %} <i class="fa fa-fw fa-check"></i> @@ -50,8 +55,10 @@ <em>{{ 'never'|_ }}</em> </td> {% endif %} - <td class="hidden-sm hidden-xs" data-value="{{ account.difference }}"> + <td class="hidden-sm hidden-xs" data-value="{{ account.difference }}" style="text-align: right;"> + <span style="margin-right:5px;"> {{ (account.difference)|formatAmount }} + </span> </td> </tr> diff --git a/resources/views/list/bills.twig b/resources/views/list/bills.twig index 2bf6f5b128..7e6d1af0b1 100644 --- a/resources/views/list/bills.twig +++ b/resources/views/list/bills.twig @@ -29,11 +29,15 @@ <span class="label label-info">{{ match }}</span> {% endfor %} </td> - <td data-value="{{ entry.amount_min }}"> + <td data-value="{{ entry.amount_min }}" style="text-align: right;"> + <span style="margin-right:5px;"> {{ entry.amount_min|formatAmount }} + </span> </td> - <td data-value="{{ entry.amount_max }}"> + <td data-value="{{ entry.amount_max }}" style="text-align: right;"> + <span style="margin-right:5px;"> {{ entry.amount_max|formatAmount }} + </span> </td> {% if entry.paidDates.count() == 0 and entry.payDates.count() == 0 and entry.active %} diff --git a/resources/views/list/journals-tasker.twig b/resources/views/list/journals-tasker.twig index daa673cfb9..a52df725be 100644 --- a/resources/views/list/journals-tasker.twig +++ b/resources/views/list/journals-tasker.twig @@ -1,6 +1,6 @@ {{ journals.render|raw }} -<table class="table table-hover table-compressed {% if sorting %}sortable-table{% endif %}"> +<table class="table table-hover table-condensed {% if sorting %}sortable-table{% endif %}"> <thead> <tr class="ignore"> <th class="hidden-xs no_select_boxes" colspan="2"> </th> @@ -26,12 +26,6 @@ {% endif %} </tr> </thead> - <!-- to be fixed: - SORTING - ATTACHMENT COUNT - SPLIT JOURNAL INDICATOR - - --> <tbody> {% for transaction in journals %} <tr class="drag" data-date="{{ transaction.date.format('Y-m-d') }}" data-id="{{ transaction.journal_id }}"> @@ -68,13 +62,13 @@ {% endif %} </td> - <td> + <td style="text-align: right;"> + <span style="margin-right:5px;"> <!-- format amount of transaction --> - {{ formatAmountWithCode(transaction.transaction_amount, transaction.transaction_currency_code) }} + {{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} <!-- and then amount of journal itself. --> - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, - transaction.transaction_currency_code, transaction.transaction_type_type) }} - + {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} + </span> </td> <td class="hidden-sm hidden-xs"> diff --git a/resources/views/list/journals-tiny-tasker.twig b/resources/views/list/journals-tiny-tasker.twig index a8eae3298d..190d28cf37 100644 --- a/resources/views/list/journals-tiny-tasker.twig +++ b/resources/views/list/journals-tiny-tasker.twig @@ -15,10 +15,9 @@ {% endif %} <span class="pull-right small"> <!-- format amount of transaction --> - {{ formatAmountWithCode(transaction.transaction_amount, transaction.transaction_currency_code) }} + {{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} <!-- and then amount of journal itself. --> - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, - transaction.transaction_currency_code, transaction.transaction_type_type) }} + {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} </span> </a> {% endfor %} diff --git a/resources/views/list/piggy-bank-events.twig b/resources/views/list/piggy-bank-events.twig index 69caeb6247..df71625b69 100644 --- a/resources/views/list/piggy-bank-events.twig +++ b/resources/views/list/piggy-bank-events.twig @@ -22,7 +22,7 @@ {% endif %} </td> - <td> + <td style="text-align: right;"> {% if event.amount < 0 %} <span class="text-danger">{{ trans('firefly.removed_amount', {amount: (event.amount)|formatAmountPlain})|raw }}</span> {% else %} diff --git a/resources/views/list/piggy-banks.twig b/resources/views/list/piggy-banks.twig index 269c675dfc..7fa3188f14 100644 --- a/resources/views/list/piggy-banks.twig +++ b/resources/views/list/piggy-banks.twig @@ -1,4 +1,14 @@ -<table class="table table-hover table-condensed" id="sortable"> +<table class="table table-hover table-condensed" id="sortable-piggy"> + <thead> + <tr> + <th colspan="2"> </th> + <th>{{ 'piggy_bank'|_ }}</th> + <th style="text-align: right;">{{ 'saved_so_far'|_ }}</th> + <th colspan="3"> </th> + <th style="text-align: right;">{{ 'target_amount'|_ }}</th> + <th style="text-align: right;">{{ 'left_to_save'|_ }}</th> + </tr> + </thead> <tbody> {% for piggyBank in piggyBanks %} <tr data-id="{{ piggyBank.id }}"> @@ -21,8 +31,8 @@ <td> <a href="{{ route('piggy-banks.show', piggyBank.id) }}" title="{{ piggyBank.order }}">{{ piggyBank.name }}</a> </td> - <td> - <span title="Saved so far">{{ piggyBank.savedSoFar|formatAmountPlain }}</span> + <td style="text-align: right;"> + <span title="Saved so far" style="text-align:right;">{{ piggyBank.savedSoFar|formatAmount }}</span> </td> <td class="hidden-sm hidden-xs" style="text-align:right;width:40px;"> {% if piggyBank.savedSoFar > 0 %} @@ -55,10 +65,12 @@ <i data-id="{{ piggyBank.id }}" class="fa fa-plus"></i></a> {% endif %} </td> - <td class="hidden-sm hidden-xs" style="width:200px;"> - <span title="Target amount">{{ piggyBank.targetamount|formatAmount }}</span> + <td class="hidden-sm hidden-xs" style="width:200px;text-align:right;"> + <span title="{{ 'target_amount'|_ }}">{{ piggyBank.targetamount|formatAmount }}</span> + </td> + <td class="hidden-sm hidden-xs" style="width:200px;text-align:right;"> {% if piggyBank.leftToSave > 0 %} - <span title="Left to save">({{ piggyBank.leftToSave|formatAmount }})</span> + <span title="{{ 'left_to_save'|_ }}">{{ piggyBank.leftToSave|formatAmount }}</span> {% endif %} </td> </tr> diff --git a/resources/views/piggy-banks/index.twig b/resources/views/piggy-banks/index.twig index 6c4afac57b..821b731c9a 100644 --- a/resources/views/piggy-banks/index.twig +++ b/resources/views/piggy-banks/index.twig @@ -9,12 +9,10 @@ <div class="col-lg-12 col-md-12 col-sm-12"> <div class="box"> <div class="box-header with-border"> - <h3 class="box-title">{{ 'piggyBanks'|_ }}</h3></div> - <div class="box-body table-responsive no-padding"> - {% include 'list/piggy-banks' %} + <h3 class="box-title">{{ 'piggyBanks'|_ }}</h3> </div> - <div class="box-footer"> - <a href="{{ route('piggy-banks.create') }}" class="btn btn-success pull-right">{{ 'new_piggy_bank'|_ }}</a> + <div class="box-body no-padding"> + {% include 'list/piggy-banks' %} </div> </div> </div> @@ -31,22 +29,22 @@ <thead> <tr> <th>{{ 'account'|_ }}</th> - <th class="hidden-sm hidden-xs">{{ 'balance'|_ }}</th> - <th>{{ 'left_for_piggy_banks'|_ }}</th> - <th class="hidden-sm hidden-xs">{{ 'sum_of_piggy_banks'|_ }}</th> - <th class="hidden-sm hidden-xs">{{ 'saved_so_far'|_ }}</th> - <th class="hidden-sm hidden-xs">{{ 'left_to_save'|_ }}</th> + <th style="text-align:right;" class="hidden-sm hidden-xs">{{ 'balance'|_ }}</th> + <th style="text-align:right;">{{ 'left_for_piggy_banks'|_ }}</th> + <th style="text-align:right;" class="hidden-sm hidden-xs">{{ 'sum_of_piggy_banks'|_ }}</th> + <th style="text-align:right;" class="hidden-sm hidden-xs">{{ 'saved_so_far'|_ }}</th> + <th style="text-align:right;" class="hidden-sm hidden-xs">{{ 'left_to_save'|_ }}</th> </tr> </thead> <tbody> {% for id,info in accounts %} <tr> <td><a href="{{ route('accounts.show',id) }}" title="{{ info.name }}">{{ info.name }}</a></td> - <td class="hidden-sm hidden-xs">{{ info.balance|formatAmount }}</td> - <td>{{ info.leftForPiggyBanks|formatAmount }}</td> - <td class="hidden-sm hidden-xs">{{ info.sumOfTargets|formatAmount }}</td> - <td class="hidden-sm hidden-xs">{{ info.sumOfSaved|formatAmount }}</td> - <td class="hidden-sm hidden-xs">{{ info.leftToSave|formatAmount }}</td> + <td style="text-align:right;" class="hidden-sm hidden-xs">{{ info.balance|formatAmount }}</td> + <td style="text-align:right;">{{ info.leftForPiggyBanks|formatAmount }}</td> + <td style="text-align:right;" class="hidden-sm hidden-xs">{{ info.sumOfTargets|formatAmount }}</td> + <td style="text-align:right;" class="hidden-sm hidden-xs">{{ info.sumOfSaved|formatAmount }}</td> + <td style="text-align:right;" class="hidden-sm hidden-xs">{{ info.leftToSave|formatAmount }}</td> </tr> {% endfor %} </tbody> diff --git a/resources/views/popup/list/journals-tasker.twig b/resources/views/popup/list/journals-tasker.twig index 36c36aeccb..d6f97dcc59 100644 --- a/resources/views/popup/list/journals-tasker.twig +++ b/resources/views/popup/list/journals-tasker.twig @@ -46,10 +46,9 @@ </td> <td> <!-- format amount of transaction --> - {{ formatAmountWithCode(transaction.transaction_amount, transaction.transaction_currency_code) }} + {{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} <!-- and then amount of journal itself. --> - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, - transaction.transaction_currency_code, transaction.transaction_type_type) }} + {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} </td> <td class="hidden-sm hidden-xs"> {{ transaction.date.formatLocalized(monthAndDayFormat) }} diff --git a/resources/views/preferences/index.twig b/resources/views/preferences/index.twig index 96c0b98b3d..65080962ba 100644 --- a/resources/views/preferences/index.twig +++ b/resources/views/preferences/index.twig @@ -45,6 +45,11 @@ </div> {% endif %} {% endfor %} + + <p class="text-info"> + <br /> + {{ 'pref_languages_locale'|_ }} + </p> </div> <!-- fiscal year --> diff --git a/resources/views/reports/budget/month.twig b/resources/views/reports/budget/month.twig index 5336f0fd18..d5efaae2f7 100644 --- a/resources/views/reports/budget/month.twig +++ b/resources/views/reports/budget/month.twig @@ -17,7 +17,7 @@ <thead> <tr> <th data-defaultsign="az">{{ 'name'|_ }}</th> - <th data-defaultsign="_19">{{ 'spent'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'spent'|_ }}</th> </tr> </thead> <tbody> @@ -27,9 +27,9 @@ <a href="{{ route('accounts.show', account.id) }}" title="{{ account.name }}">{{ account.name }}</a> </td> {% if accountSummary[account.id] %} - <td data-value="{{ accountSummary[account.id] }}">{{ accountSummary[account.id]|formatAmount }}</td> + <td data-value="{{ accountSummary[account.id] }}" style="text-align: right;">{{ accountSummary[account.id]|formatAmount }}</td> {% else %} - <td data-value="0">{{ 0|formatAmount }}</td> + <td data-value="0" style="text-align: right;">{{ 0|formatAmount }}</td> {% endif %} </tr> {% endfor %} @@ -47,7 +47,7 @@ <thead> <tr> <th data-defaultsign="az">{{ 'name'|_ }}</th> - <th data-defaultsign="_19">{{ 'spent'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'spent'|_ }}</th> </tr> </thead> <tbody> @@ -57,9 +57,9 @@ <a href="{{ route('budgets.show', budget.id) }}" title="{{ budget.name }}">{{ budget.name }}</a> </td> {% if budgetSummary[budget.id] %} - <td data-value="{{ budgetSummary[budget.id] }}">{{ budgetSummary[budget.id]|formatAmount }}</td> + <td data-value="{{ budgetSummary[budget.id] }}" style="text-align: right;">{{ budgetSummary[budget.id]|formatAmount }}</td> {% else %} - <td data-value="0">{{ 0|formatAmount }}</td> + <td data-value="0" style="text-align: right;">{{ 0|formatAmount }}</td> {% endif %} </tr> {% endfor %} @@ -75,10 +75,12 @@ <h3 class="box-title">{{ 'expense_per_budget'|_ }}</h3> </div> <div class="box-body"> - <canvas id="budgets-out-pie-chart" style="margin:0 auto;" height="150" width="150"></canvas> + <div style="width:75%;margin:0 auto;"> + <canvas id="budgets-out-pie-chart" style="margin:0 auto;"></canvas> + </div> <label style="font-weight:normal;"> <input type="checkbox" id="budgets-out-pie-chart-checked"> - <small>{{ 'include_not_in_budget'|_ }}</small> + <small>{{ 'include_expense_not_in_budget'|_ }}</small> </label> </div> </div> @@ -90,10 +92,12 @@ <h3 class="box-title">{{ 'expense_per_account'|_ }}</h3> </div> <div class="box-body"> - <canvas id="accounts-out-pie-chart" style="margin:0 auto;" height="150" width="150"></canvas> + <div style="width:75%;margin:0 auto;"> + <canvas id="accounts-out-pie-chart" style="margin:0 auto;"></canvas> + </div> <label style="font-weight:normal;"> <input type="checkbox" id="accounts-out-pie-chart-checked"> - <small>{{ 'include_not_in_budget'|_ }}</small> + <small>{{ 'include_expense_not_in_account'|_ }}</small> </label> </div> </div> @@ -129,8 +133,8 @@ <thead> <tr> <th data-defaultsign="az">{{ 'account'|_ }}</th> - <th data-defaultsign="_19">{{ 'spent_average'|_ }}</th> - <th data-defaultsign="_19">{{ 'total'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'spent_average'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'total'|_ }}</th> <th data-defaultsign="_19">{{ 'transaction_count'|_ }}</th> </tr> </thead> @@ -144,10 +148,10 @@ <td data-value="{{ row.name }}"> <a href="{{ route('accounts.show', row.id) }}">{{ row.name }}</a> </td> - <td data-value="{{ row.average }}"> + <td data-value="{{ row.average }}" style="text-align: right;"> {{ row.average|formatAmount }} </td> - <td data-value="{{ row.sum }}"> + <td data-value="{{ row.sum }}" style="text-align: right;"> {{ row.sum|formatAmount }} </td> <td data-value="{{ row.count }}"> @@ -184,7 +188,7 @@ <th data-defaultsort="disabled">{{ 'description'|_ }}</th> <th data-defaultsign="month">{{ 'date'|_ }}</th> <th data-defaultsign="az">{{ 'account'|_ }}</th> - <th data-defaultsign="_19">{{ 'amount'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'amount'|_ }}</th> </tr> </thead> <tbody> @@ -211,7 +215,7 @@ {{ row.opposing_account_name }} </a> </td> - <td data-value="{{ row.transaction_amount }}"> + <td data-value="{{ row.transaction_amount }}" style="text-align: right;"> {{ row.transaction_amount|formatAmount }} </td> </tr> diff --git a/resources/views/reports/category/month.twig b/resources/views/reports/category/month.twig index 76df238013..4bf6652694 100644 --- a/resources/views/reports/category/month.twig +++ b/resources/views/reports/category/month.twig @@ -17,8 +17,8 @@ <thead> <tr> <th data-defaultsign="az">{{ 'name'|_ }}</th> - <th data-defaultsign="_19">{{ 'earned'|_ }}</th> - <th data-defaultsign="_19">{{ 'spent'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'earned'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'spent'|_ }}</th> </tr> </thead> <tbody> @@ -28,14 +28,14 @@ <a href="{{ route('accounts.show', account.id) }}" title="{{ account.name }}">{{ account.name }}</a> </td> {% if accountSummary[account.id] %} - <td data-value="{{ accountSummary[account.id].earned }}">{{ accountSummary[account.id].earned|formatAmount }}</td> + <td data-value="{{ accountSummary[account.id].earned }}" style="text-align: right;">{{ accountSummary[account.id].earned|formatAmount }}</td> {% else %} - <td data-value="0">{{ 0|formatAmount }}</td> + <td data-value="0" style="text-align: right;">{{ 0|formatAmount }}</td> {% endif %} {% if accountSummary[account.id] %} - <td data-value="{{ accountSummary[account.id].spent }}">{{ accountSummary[account.id].spent|formatAmount }}</td> + <td data-value="{{ accountSummary[account.id].spent }}" style="text-align: right;">{{ accountSummary[account.id].spent|formatAmount }}</td> {% else %} - <td data-value="0">{{ 0|formatAmount }}</td> + <td data-value="0" style="text-align: right;">{{ 0|formatAmount }}</td> {% endif %} </tr> {% endfor %} @@ -53,8 +53,8 @@ <thead> <tr> <th data-defaultsign="az">{{ 'name'|_ }}</th> - <th data-defaultsign="_19">{{ 'earned'|_ }}</th> - <th data-defaultsign="_19">{{ 'spent'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'earned'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'spent'|_ }}</th> </tr> </thead> <tbody> @@ -64,14 +64,14 @@ <a href="{{ route('categories.show', category.id) }}" title="{{ category.name }}">{{ category.name }}</a> </td> {% if categorySummary[category.id] %} - <td data-value="{{ categorySummary[category.id].earned }}">{{ categorySummary[category.id].earned|formatAmount }}</td> + <td data-value="{{ categorySummary[category.id].earned }}" style="text-align: right;">{{ categorySummary[category.id].earned|formatAmount }}</td> {% else %} - <td data-value="0">{{ 0|formatAmount }}</td> + <td data-value="0" style="text-align: right;">{{ 0|formatAmount }}</td> {% endif %} {% if categorySummary[category.id] %} - <td data-value="{{ categorySummary[category.id].spent }}">{{ categorySummary[category.id].spent|formatAmount }}</td> + <td data-value="{{ categorySummary[category.id].spent }}" style="text-align: right;">{{ categorySummary[category.id].spent|formatAmount }}</td> {% else %} - <td data-value="0">{{ 0|formatAmount }}</td> + <td data-value="0" style="text-align: right;">{{ 0|formatAmount }}</td> {% endif %} </tr> {% endfor %} @@ -90,7 +90,7 @@ <canvas id="categories-in-pie-chart" style="margin:0 auto;" height="150" width="150"></canvas> <label style="font-weight:normal;"> <input type="checkbox" id="categories-in-pie-chart-checked"> - <small>{{ 'include_not_in_category'|_ }}</small> + <small>{{ 'include_income_not_in_category'|_ }}</small> </label> </div> </div> @@ -104,7 +104,7 @@ <canvas id="categories-out-pie-chart" style="margin:0 auto;" height="150" width="150"></canvas> <label style="font-weight:normal;"> <input type="checkbox" id="categories-out-pie-chart-checked"> - <small>{{ 'include_not_in_category'|_ }}</small> + <small>{{ 'include_expense_not_in_category'|_ }}</small> </label> </div> </div> @@ -119,7 +119,7 @@ <canvas id="accounts-in-pie-chart" style="margin:0 auto;" height="150" width="150"></canvas> <label style="font-weight:normal;"> <input type="checkbox" id="accounts-in-pie-chart-checked"> - <small>{{ 'include_not_in_category'|_ }}</small> + <small>{{ 'include_income_not_in_account'|_ }}</small> </label> </div> </div> @@ -133,7 +133,7 @@ <canvas id="accounts-out-pie-chart" style="margin:0 auto;" height="150" width="150"></canvas> <label style="font-weight:normal;"> <input type="checkbox" id="accounts-out-pie-chart-checked"> - <small>{{ 'include_not_in_category'|_ }}</small> + <small>{{ 'include_expense_not_in_account'|_ }}</small> </label> </div> </div> @@ -164,8 +164,8 @@ <thead> <tr> <th data-defaultsign="az">{{ 'account'|_ }}</th> - <th data-defaultsign="_19">{{ 'spent_average'|_ }}</th> - <th data-defaultsign="_19">{{ 'total'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'spent_average'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'total'|_ }}</th> <th data-defaultsign="_19">{{ 'transaction_count'|_ }}</th> </tr> </thead> @@ -179,10 +179,10 @@ <td data-value="{{ row.name }}"> <a href="{{ route('accounts.show', row.id) }}">{{ row.name }}</a> </td> - <td data-value="{{ row.average }}"> + <td data-value="{{ row.average }}" style="text-align: right;"> {{ row.average|formatAmount }} </td> - <td data-value="{{ row.sum }}"> + <td data-value="{{ row.sum }}" style="text-align: right;"> {{ row.sum|formatAmount }} </td> <td data-value="{{ row.count }}"> @@ -219,7 +219,7 @@ <th data-defaultsort="disabled">{{ 'description'|_ }}</th> <th data-defaultsign="month">{{ 'date'|_ }}</th> <th data-defaultsign="az">{{ 'account'|_ }}</th> - <th data-defaultsign="_19">{{ 'amount'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'amount'|_ }}</th> </tr> </thead> <tbody> @@ -246,7 +246,7 @@ {{ row.opposing_account_name }} </a> </td> - <td data-value="{{ row.transaction_amount }}"> + <td data-value="{{ row.transaction_amount }}" style="text-align: right;"> {{ row.transaction_amount|formatAmount }} </td> </tr> diff --git a/resources/views/reports/partials/accounts.twig b/resources/views/reports/partials/accounts.twig index ef3ad787e7..3468f4c1d0 100644 --- a/resources/views/reports/partials/accounts.twig +++ b/resources/views/reports/partials/accounts.twig @@ -2,9 +2,9 @@ <thead> <tr> <th data-defaultsign="az">{{ 'name'|_ }}</th> - <th data-defaultsign="_19" class="hidden-xs">{{ 'balanceStart'|_ }}</th> - <th data-defaultsign="_19" class="hidden-xs">{{ 'balanceEnd'|_ }}</th> - <th data-defaultsign="_19">{{ 'difference'|_ }}</th> + <th data-defaultsign="_19" class="hidden-xs" style="text-align: right;">{{ 'balanceStart'|_ }}</th> + <th data-defaultsign="_19" class="hidden-xs" style="text-align: right;">{{ 'balanceEnd'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'difference'|_ }}</th> </tr> </thead> <tbody> @@ -13,18 +13,18 @@ <td data-value="{{ account.name }}"> <a href="{{ route('accounts.show',account.id) }}" title="{{ account.name }}">{{ account.name }}</a> </td> - <td class="hidden-xs" data-value="{{ account.startBalance }}">{{ account.startBalance|formatAmount }}</td> - <td class="hidden-xs" data-value="{{ account.endBalance }}">{{ account.endBalance|formatAmount }}</td> - <td data-value="{{ (account.endBalance - account.startBalance) }}">{{ (account.endBalance - account.startBalance)|formatAmount }}</td> + <td class="hidden-xs" data-value="{{ account.startBalance }}" style="text-align: right;">{{ account.startBalance|formatAmount }}</td> + <td class="hidden-xs" data-value="{{ account.endBalance }}" style="text-align: right;">{{ account.endBalance|formatAmount }}</td> + <td style="text-align: right;" data-value="{{ (account.endBalance - account.startBalance) }}">{{ (account.endBalance - account.startBalance)|formatAmount }}</td> </tr> {% endfor %} </tbody> <tfoot> <tr> <td><em>{{ 'sumOfSums'|_ }}</em></td> - <td class="hidden-xs">{{ accountReport.getStart|formatAmount }}</td> - <td class="hidden-xs">{{ accountReport.getEnd|formatAmount }}</td> - <td>{{ accountReport.getDifference|formatAmount }}</td> + <td class="hidden-xs" style="text-align: right;">{{ accountReport.getStart|formatAmount }}</td> + <td class="hidden-xs" style="text-align: right;">{{ accountReport.getEnd|formatAmount }}</td> + <td style="text-align: right;">{{ accountReport.getDifference|formatAmount }}</td> </tr> </tfoot> </table> diff --git a/resources/views/reports/partials/balance.twig b/resources/views/reports/partials/balance.twig index 58fdf45cfc..c2c6e1a323 100644 --- a/resources/views/reports/partials/balance.twig +++ b/resources/views/reports/partials/balance.twig @@ -4,9 +4,9 @@ <tr> <th colspan="2">{{ 'budgets'|_ }}</th> {% for account in balance.getBalanceHeader.getAccounts %} - <th class="hidden-xs"><a href="{{ route('accounts.show',account.id) }}">{{ account.name }}</a></th> + <th class="hidden-xs" style="text-align: right;"><a href="{{ route('accounts.show',account.id) }}">{{ account.name }}</a></th> {% endfor %} - <th> + <th style="text-align: right;"> {{ 'leftInBudget'|_ }} </th> </tr> @@ -28,7 +28,7 @@ </span> {% endif %} </td> - <td> + <td style="text-align: right;"> {% if(balanceLine.getBudget.amount) %} {{ balanceLine.getBudget.amount|formatAmount }} {% else %} @@ -40,7 +40,7 @@ {% endif %} {% for balanceEntry in balanceLine.getBalanceEntries %} - <td class="hidden-xs"> + <td class="hidden-xs" style="text-align: right;"> {% if balanceEntry.getSpent != 0 %} <span class="text-danger">{{ (balanceEntry.getSpent)|formatAmountPlain }}</span> <i class="fa fa-fw text-muted fa-info-circle firefly-info-button" data-location="balance-amount" @@ -49,11 +49,11 @@ {% endif %} {% if balanceEntry.getLeft != 0 %} - <span class="text-success">{{ (balanceEntry.getLeft)|formatAmountPlain }}</span> + <span class="text-success" style="text-align: right;">{{ (balanceEntry.getLeft)|formatAmountPlain }}</span> {% endif %} </td> {% endfor %} - <td> + <td style="text-align: right;"> {{ balanceLine.leftOfRepetition|formatAmount }} </td> </tr> diff --git a/resources/views/reports/partials/bills.twig b/resources/views/reports/partials/bills.twig index fe234655ef..05f7f02839 100644 --- a/resources/views/reports/partials/bills.twig +++ b/resources/views/reports/partials/bills.twig @@ -7,10 +7,10 @@ <thead> <tr> <th data-defaultsign="az">{{ trans('form.name') }}</th> - <th data-defaultsign="_19" class="hidden-xs">{{ trans('form.amount_min') }}</th> - <th data-defaultsign="_19" class="hidden-xs">{{ trans('form.amount_max') }}</th> - <th data-defaultsign="_19">{{ trans('form.amount') }}</th> - <th data-defaultsign="_19">{{ trans('form.under') }}</th> + <th data-defaultsign="_19" class="hidden-xs" style="text-align: right;">{{ trans('form.amount_min') }}</th> + <th data-defaultsign="_19" class="hidden-xs" style="text-align: right;">{{ trans('form.amount_max') }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ trans('form.amount') }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ trans('form.under') }}</th> </tr> </thead> <tbody> @@ -19,10 +19,10 @@ <td data-value="{{ line.getBill.name }}"> <a href="{{ route('bills.show',line.getBill.id) }}">{{ line.getBill.name }}</a> </td> - <td class="hidden-xs" data-value="{{ line.getMin }}">{{ line.getMin|formatAmount }}</td> - <td class="hidden-xs" data-value="{{ line.getMax }}">{{ line.getMax|formatAmount }}</td> + <td class="hidden-xs" data-value="{{ line.getMin }}" style="text-align: right;">{{ line.getMin|formatAmount }}</td> + <td class="hidden-xs" data-value="{{ line.getMax }}" style="text-align: right;">{{ line.getMax|formatAmount }}</td> {% if line.isHit %} - <td data-value="{{ line.getAmount }}"> + <td data-value="{{ line.getAmount }}" style="text-align: right;"> <a href="{{ route('transactions.show', line.getTransactionJournalId) }}"> {{ line.getAmount|formatAmount }} </a> @@ -34,7 +34,7 @@ {% if not line.isActive %} <td data-value="-1"> </td> {% endif %} - <td data-value="{{ (line.getMax - line.getAmount) }}"> + <td data-value="{{ (line.getMax - line.getAmount) }}" style="text-align: right;"> {% if line.isActive %} {{ (line.getMax + line.getAmount)|formatAmount }} {% endif %} diff --git a/resources/views/reports/partials/budget-period.twig b/resources/views/reports/partials/budget-period.twig index 86d5f98aed..e5586d17b0 100644 --- a/resources/views/reports/partials/budget-period.twig +++ b/resources/views/reports/partials/budget-period.twig @@ -3,9 +3,9 @@ <tr> <th data-defaultsign="az" colspan="2">{{ 'budget'|_ }}</th> {% for period in periods %} - <th data-defaultsign="_19">{{ period }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ period }}</th> {% endfor %} - <th data-defaultsign="_19">{{ 'sum'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'sum'|_ }}</th> </tr> </thead> <tbody> @@ -19,17 +19,17 @@ </td> {% for key, period in periods %} {% if(info.entries[key]) %} - <td data-value="{{ info.entries[key] }}"> + <td data-value="{{ info.entries[key] }}" style="text-align: right;"> {{ info.entries[key]|formatAmount }} </td> {% else %} - <td data-value="0"> + <td data-value="0" style="text-align: right;"> {{ 0|formatAmount }} </td> {% endif %} {% endfor %} - <td data-value="{{ info.sum }}"> + <td data-value="{{ info.sum }}" style="text-align: right;"> {{ info.sum|formatAmount }} </td> </tr> diff --git a/resources/views/reports/partials/budgets.twig b/resources/views/reports/partials/budgets.twig index 8a06c858b2..be4b938c4b 100644 --- a/resources/views/reports/partials/budgets.twig +++ b/resources/views/reports/partials/budgets.twig @@ -3,10 +3,11 @@ <tr> <th data-defaultsign="az">{{ 'budget'|_ }}</th> <th data-defaultsign="month" class="hidden-xs">{{ 'date'|_ }}</th> - <th data-defaultsign="_19">{{ 'budgeted'|_ }}</th> - <th data-defaultsign="_19">{{ 'spent'|_ }}</th> - <th data-defaultsign="_19">{{ 'left'|_ }}</th> - <th data-defaultsign="_19">{{ 'overspent'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'budgeted'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'spent'|_ }}</th> + <th data-defaultsort="disabled"> </th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'left'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'overspent'|_ }}</th> </tr> </thead> <tbody> @@ -24,12 +25,12 @@ {% endif %} - {% if budgetLine.getRepetition.id %} - <td class="hidden-xs" data-value="{{ budgetLine.getRepetition.startdate.format('Y-m-d') }}"> - <a href="{{ route('budgets.show.repetition', [budgetLine.getBudget.id, budgetLine.getRepetition.id]) }}"> - {{ budgetLine.getRepetition.startdate.formatLocalized(monthAndDayFormat) }} + {% if budgetLine.getBudgetLimit.id %} + <td class="hidden-xs" data-value="{{ budgetLine.getBudgetLimit.start_date.format('Y-m-d') }}"> + <a href="{{ route('budgets.show.limit', [budgetLine.getBudget.id, budgetLine.getBudgetLimit.id]) }}"> + {{ budgetLine.getBudgetLimit.start_date.formatLocalized(monthAndDayFormat) }} — - {{ budgetLine.getRepetition.enddate.formatLocalized(monthAndDayFormat) }} + {{ budgetLine.getBudgetLimit.end_date.formatLocalized(monthAndDayFormat) }} </a> </td> {% else %} @@ -39,34 +40,42 @@ {% endif %} - {% if budgetLine.getRepetition.id %} - <td data-value="{{ budgetLine.getRepetition.amount }}"> - {{ budgetLine.getRepetition.amount|formatAmount }} + {% if budgetLine.getBudgetLimit.id %} + <td data-value="{{ budgetLine.getBudgetLimit.amount }}" style="text-align: right;"> + {{ budgetLine.getBudgetLimit.amount|formatAmount }} </td> {% else %} - <td data-value="0"> + <td data-value="0" style="text-align: right;"> {{ 0|formatAmount }} </td> {% endif %} - <td data-value="{{ budgetLine.getSpent }}"> + <td data-value="{{ budgetLine.getSpent }}" style="text-align: right;"> {% if budgetLine.getSpent != 0 %} {{ budgetLine.getSpent|formatAmount }} - <i class="fa fa-fw text-muted fa-info-circle firefly-info-button" + <!-- <i class="fa fa-fw text-muted fa-info-circle firefly-info-button" data-location="budget-spent-amount" data-budget-id="{{ budgetLine.getBudget.id }}"></i> + --> {% endif %} {% if budgetLine.getSpent == 0 %} {{ budgetLine.getSpent|formatAmount }} {% endif %} - </td> - <td data-value="{{ budgetLine.getLeft }}"> + <td> + {% if budgetLine.getSpent != 0 %} + <i class="fa fa-fw text-muted fa-info-circle firefly-info-button" + data-location="budget-spent-amount" data-budget-id="{{ budgetLine.getBudget.id }}"></i> + + {% endif %} + </td> + + <td data-value="{{ budgetLine.getLeft }}" style="text-align: right;"> {% if(budgetLine.getOverspent == 0) %} {{ budgetLine.getLeft|formatAmount }} {% endif %} </td> - <td data-value="{{ budgetLine.getOverspent }}"> + <td data-value="{{ budgetLine.getOverspent }}" style="text-align: right;"> {% if budgetLine.getOverspent != 0 %} {{ budgetLine.getOverspent|formatAmount }} {% endif %} @@ -78,8 +87,8 @@ <tr> <td><em>{{ 'sum'|_ }}</em></td> <td class="hidden-xs"> </td> - <td>{{ budgets.getBudgeted|formatAmount }}</td> - <td> + <td style="text-align: right;">{{ budgets.getBudgeted|formatAmount }}</td> + <td style="text-align: right;"> {% if budgets.getSpent != 0 %} <span class="text-danger">{{ budgets.getSpent|formatAmountPlain }}</span> {% endif %} @@ -87,8 +96,9 @@ {{ budgets.getSpent|formatAmount }} {% endif %} </td> - <td>{{ budgets.getLeft|formatAmount }}</td> - <td><span class="text-danger">{{ budgets.getOverspent|formatAmountPlain }}</span></td> + <td> </td> + <td style="text-align: right;">{{ budgets.getLeft|formatAmount }}</td> + <td style="text-align: right;"><span class="text-danger">{{ budgets.getOverspent|formatAmountPlain }}</span></td> </tr> </tfoot> </table> diff --git a/resources/views/reports/partials/categories.twig b/resources/views/reports/partials/categories.twig index d5bfcb4df8..eec4ac0843 100644 --- a/resources/views/reports/partials/categories.twig +++ b/resources/views/reports/partials/categories.twig @@ -2,7 +2,8 @@ <thead> <tr> <th>{{ 'category'|_ }}</th> - <th colspan="2">{{ 'spent'|_ }}</th> + <th style="text-align: right;">{{ 'spent'|_ }}</th> + <th> </th> </tr> </thead> <tbody> @@ -17,7 +18,7 @@ <td> <a href="{{ route('categories.show',id) }}">{{ category.name }}</a> </td> - <td>{{ category.spent|formatAmount }}</td> + <td style="text-align: right;">{{ category.spent|formatAmount }}</td> <td style="width:20px;"> <i class="fa fa-fw fa-info-circle text-muted firefly-info-button" data-location="category-entry" data-category-id="{{ id }}" @@ -37,7 +38,8 @@ <tr> <td><em>{{ 'sum'|_ }}</em></td> - <td>{{ sum|formatAmount }}</td> + <td style="text-align: right;">{{ sum|formatAmount }}</td> + <td> </td> </tr> </tfoot> </table> diff --git a/resources/views/reports/partials/category-period.twig b/resources/views/reports/partials/category-period.twig index d24e0ec09f..8228327b64 100644 --- a/resources/views/reports/partials/category-period.twig +++ b/resources/views/reports/partials/category-period.twig @@ -3,9 +3,9 @@ <tr> <th data-defaultsign="az" colspan="2">{{ 'category'|_ }}</th> {% for period in periods %} - <th data-defaultsign="_19">{{ period }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ period }}</th> {% endfor %} - <th data-defaultsign="_19">{{ 'sum'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'sum'|_ }}</th> </tr> </thead> <tbody> @@ -20,11 +20,11 @@ {% for key, period in periods %} {# income first #} {% if(info.entries[key]) %} - <td data-value="{{ info.entries[key] }}"> + <td data-value="{{ info.entries[key] }}" style="text-align: right;"> {{ info.entries[key]|formatAmount }} </td> {% else %} - <td data-value="0"> + <td data-value="0" style="text-align: right;"> {{ 0|formatAmount }} </td> {% endif %} @@ -32,11 +32,11 @@ {# if sum of income, display: #} {% if info.sum %} - <td data-value="{{ info.sum }}"> + <td data-value="{{ info.sum }}" style="text-align: right;"> {{ info.sum|formatAmount }} </td> {% else %} - <td data-value="0"> + <td data-value="0" style="text-align: right;"> {{ 0|formatAmount }} </td> {% endif %} diff --git a/resources/views/reports/partials/expenses.twig b/resources/views/reports/partials/expenses.twig deleted file mode 100644 index e1d7956087..0000000000 --- a/resources/views/reports/partials/expenses.twig +++ /dev/null @@ -1,41 +0,0 @@ -<table class="table table-hover"> - <tbody> - {% set sum = 0 %} - {% for expense in expenses %} - {% set sum = sum + expense.sum %} - {% if loop.index > listLength %} - <tr class="overListLength"> - {% else %} - <tr> - {% endif %} - <td> - <a href="{{ route('accounts.show',expense.id) }}">{{ expense.name }}</a> - {% if expense.count > 1 %} - <br/> - <small> - {{ expense.count }} {{ 'transactions'|_|lower }} - <i class="fa fa-fw text-muted fa-info-circle firefly-info-button" data-location="expense-entry" - data-account-id="{{ expense.id }}"></i> - </small> - {% endif %} - </td> - <td> - {{ (expense.sum)|formatAmount }} - </td> - </tr> - {% endfor %} - </tbody> - <tfoot> - {% if expenses|length > listLength %} - <tr> - <td colspan="2" class="active"> - <a href="#" class="listLengthTrigger">{{ trans('firefly.show_full_list',{number:incomeTopLength}) }}</a> - </td> - </tr> - {% endif %} - <tr> - <td><em>{{ 'sum'|_ }}</em></td> - <td>{{ (sum)|formatAmount }}</td> - </tr> - </tfoot> -</table> diff --git a/resources/views/reports/partials/income-expenses.twig b/resources/views/reports/partials/income-expenses.twig new file mode 100644 index 0000000000..3dcb0f0614 --- /dev/null +++ b/resources/views/reports/partials/income-expenses.twig @@ -0,0 +1,58 @@ +<table class="table table-hover sortable"> + <thead> + <tr> + <th data-defaultsign="az">{{ 'name'|_ }}</th> + <th data-defaultsign="_19" style="text-align: right;">{{ 'total'|_ }}</th> + <th data-defaultsign="_19" class="hidden-xs" style="text-align: right;">{{ 'average'|_ }}</th> + <th data-defaultsort="disabled"></th> + </tr> + </thead> + <tbody> + {% set sum = 0 %} + {% for entry in entries %} + {% set sum = sum + entry.sum %} + {% if loop.index > listLength %} + <tr class="overListLength"> + {% else %} + <tr> + {% endif %} + <td data-value="{{ entry.name }}"> + <a href="{{ route('accounts.show',entry.id) }}">{{ entry.name }}</a> + {% if entry.count > 1 %} + <br/> + <small> + {{ entry.count }} {{ 'transactions'|_|lower }} + </small> + {% endif %} + </td> + <td data-value="{{ entry.sum }}" style="text-align: right;"> + {{ (entry.sum)|formatAmount }} + </td> + <td class="hidden-xs" data-value="{{ entry.average }}" style="text-align: right;"> + {% if entry.count > 1 %} + {{ entry.average|formatAmount }} + {% else %} + — + {% endif %} + </td> + <td> + <i class="fa fa-fw text-muted fa-info-circle firefly-info-button" data-location="{{ type }}" + data-account-id="{{ entry.id }}"></i> + </td> + </tr> + {% endfor %} + </tbody> + <tfoot> + {% if entries|length > listLength %} + <tr> + <td colspan="3" class="active"> + <a href="#" class="listLengthTrigger">{{ trans('firefly.show_full_list',{number:incomeTopLength}) }}</a> + </td> + </tr> + {% endif %} + <tr> + <td><em>{{ 'sum'|_ }}</em></td> + <td style="text-align: right;">{{ (sum)|formatAmount }}</td> + </tr> + </tfoot> +</table> diff --git a/resources/views/reports/partials/income.twig b/resources/views/reports/partials/income.twig deleted file mode 100644 index be2e37dcd0..0000000000 --- a/resources/views/reports/partials/income.twig +++ /dev/null @@ -1,41 +0,0 @@ -<table class="table table-hover"> - <tbody> - {% set sum = 0 %} - {% for row in income %} - {% set sum = sum + row.sum %} - {% if loop.index > listLength %} - <tr class="overListLength"> - {% else %} - <tr> - {% endif %} - <td> - <a href="{{ route('accounts.show',row.id) }}" title="{{ row.name }}">{{ row.name }}</a> - {% if row.count > 1 %} - <br/> - <small> - {{ row.count }} {{ 'transactions'|_|lower }} - <i class="fa fa-fw text-muted fa-info-circle firefly-info-button" data-location="income-entry" - data-account-id="{{ row.id }}"></i> - </small> - - {% endif %} - </td> - - <td>{{ row.sum|formatAmount }}</td> - </tr> - {% endfor %} - </tbody> - <tfoot> - {% if income|length > listLength %} - <tr> - <td colspan="2" class="active"> - <a href="#" class="listLengthTrigger">{{ trans('firefly.show_full_list',{ number:listLength } ) }}</a> - </td> - </tr> - {% endif %} - <tr> - <td><em>{{ 'sum'|_ }}</em></td> - <td>{{ sum|formatAmount }}</td> - </tr> - </tfoot> -</table> diff --git a/resources/views/reports/partials/journals-audit-tasker.twig b/resources/views/reports/partials/journals-audit-tasker.twig index 50e9ed6a00..0c9fd25adc 100644 --- a/resources/views/reports/partials/journals-audit-tasker.twig +++ b/resources/views/reports/partials/journals-audit-tasker.twig @@ -7,9 +7,9 @@ <th class="hide-icon"> </th> <th class="hide-description">{{ trans('list.description') }}</th> - <th class="hide-balance_before">{{ trans('list.balance_before') }}</th> - <th class="hide-amount">{{ trans('list.amount') }}</th> - <th class="hide-balance_after">{{ trans('list.balance_after') }}</th> + <th class="hide-balance_before" style="text-align: right;">{{ trans('list.balance_before') }}</th> + <th class="hide-amount" style="text-align: right;">{{ trans('list.amount') }}</th> + <th class="hide-balance_after" style="text-align: right;">{{ trans('list.balance_after') }}</th> <th class="hide-date">{{ trans('list.date') }}</th> <th class="hide-book_date">{{ trans('list.book_date') }}</th> @@ -57,15 +57,14 @@ {% endif %} </a> </td> - <td class="hide-balance_before">{{ transaction.before|formatAmount }}</td> - <td class="hide-amount"> + <td class="hide-balance_before" style="text-align: right;">{{ transaction.before|formatAmount }}</td> + <td class="hide-amount" style="text-align: right;"> <!-- format amount of transaction --> - {{ formatAmountWithCode(transaction.transaction_amount, transaction.transaction_currency_code) }} + {{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} <!-- and then amount of journal itself. --> - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, - transaction.transaction_currency_code, transaction.transaction_type_type) }} + {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} </td> - <td class="hide-balance_after">{{ transaction.after|formatAmount }}</td> + <td class="hide-balance_after" style="text-align: right;">{{ transaction.after|formatAmount }}</td> <td class="hide-date">{{ transaction.date.formatLocalized(monthAndDayFormat) }}</td> <td class="hide-book_date"> diff --git a/resources/views/reports/partials/operations.twig b/resources/views/reports/partials/operations.twig index 12fb1928f7..60ee342bb9 100644 --- a/resources/views/reports/partials/operations.twig +++ b/resources/views/reports/partials/operations.twig @@ -1,14 +1,14 @@ <table class="table table-hover"> <tr> <td>{{ 'in'|_ }}</td> - <td>{{ incomeSum|formatAmount }}</td> + <td style="text-align: right;">{{ incomeSum|formatAmount }}</td> </tr> <tr> <td>{{ 'out'|_ }}</td> - <td>{{ expensesSum|formatAmount }}</td> + <td style="text-align: right;">{{ expensesSum|formatAmount }}</td> </tr> <tr> <td>{{ 'difference'|_ }}</td> - <td>{{ (incomeSum + expensesSum)|formatAmount }}</td> + <td style="text-align: right;">{{ (incomeSum + expensesSum)|formatAmount }}</td> </tr> </table> diff --git a/resources/views/rules/index.twig b/resources/views/rules/index.twig index 6500bc827d..08b84c0bff 100644 --- a/resources/views/rules/index.twig +++ b/resources/views/rules/index.twig @@ -28,7 +28,7 @@ {% if ruleGroup.active %} {{ ruleGroup.title }} {% else %} - <s>{{ ruleGroup.title }}</s> (inactive) + <s>{{ ruleGroup.title }}</s> ({{ 'inactive'|_|lower }}) {% endif %} </h3> diff --git a/resources/views/search/partials/transactions.twig b/resources/views/search/partials/transactions.twig index 279a336a3c..587c9b432d 100644 --- a/resources/views/search/partials/transactions.twig +++ b/resources/views/search/partials/transactions.twig @@ -40,10 +40,9 @@ </td> <td data-value="{{ transaction.transaction_amount }}"> <!-- format amount of transaction --> - {{ formatAmountWithCode(transaction.transaction_amount, transaction.transaction_currency_code) }} + {{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} <!-- and then amount of journal itself. --> - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, - transaction.transaction_currency_code, transaction.transaction_type_type) }} + {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} </td> diff --git a/resources/views/tags/create.twig b/resources/views/tags/create.twig index 95b99f7d9d..1864f85843 100644 --- a/resources/views/tags/create.twig +++ b/resources/views/tags/create.twig @@ -80,7 +80,9 @@ var zoomLevel = 6; {% endif %} + var apiKey = "{{ apiKey }}"; + </script> - <script src="https://maps.googleapis.com/maps/api/js?v=3"></script> - <script src="js/ff/tags/create.js"></script> + <script src="https://maps.googleapis.com/maps/api/js?v=3&key={{ apiKey }}"></script> + <script src="js/ff/tags/create-edit.js"></script> {% endblock %} diff --git a/resources/views/tags/edit.twig b/resources/views/tags/edit.twig index e6a089d269..99366b5546 100644 --- a/resources/views/tags/edit.twig +++ b/resources/views/tags/edit.twig @@ -59,12 +59,13 @@ {% block scripts %} <script type="text/javascript"> {% if Input.old('latitude') %} - var latitude = "{{ Input.old('latitude') }}"; + var latitude = {{ Input.old('latitude') }}; {% else %} - var latitude = "52.3167"; + var latitude = {{ tag.latitude|default("52.3167") }}; {% endif %} - {% if Input.old('latitude') and Input.old('longitude') and Input.old('zoomLevel') %} + {% if (Input.old('latitude') and Input.old('longitude') and Input.old('zoomLevel')) + or (tag.latitude and tag.longitude and tag.zoomLevel) %} var doPlaceMarker = true; {% else %} var doPlaceMarker = false; @@ -73,16 +74,18 @@ {% if Input.old('longitude') %} var longitude = "{{ Input.old('longitude') }}"; {% else %} - var longitude = "5.5500"; + var longitude = {{ tag.longitude|default("5.5500") }}; {% endif %} {% if Input.old('zoomLevel') %} var zoomLevel = {{ Input.old('zoomLevel') }}; {% else %} - var zoomLevel = 6; + var zoomLevel = {{ tag.zoomLevel|default("6") }}; {% endif %} + var apiKey = "{{ apiKey }}"; + </script> - <script src="https://maps.googleapis.com/maps/api/js?v=3"></script> - <script src="js/ff/tags/edit.js"></script> + <script src="https://maps.googleapis.com/maps/api/js?v=3&key={{ apiKey }}"></script> + <script src="js/ff/tags/create-edit.js"></script> {% endblock %} diff --git a/resources/views/tags/index.twig b/resources/views/tags/index.twig index 78512e056d..1baaace293 100644 --- a/resources/views/tags/index.twig +++ b/resources/views/tags/index.twig @@ -9,7 +9,7 @@ <div class="col-lg-12"> <div class="box"> <div class="box-header with-border"> - <h3 class="box-title">Tags</h3> + <h3 class="box-title">{{ 'tags'|_ }}</h3> </div> <div class="box-body"> <p> diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index d9b8242094..f20e920b98 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -285,8 +285,9 @@ </td> <td> - {{ formatAmountWithCode(transaction.source_account_before,journal.transactionCurrency.code) }} - ⟶ {{ formatAmountWithCode(transaction.source_account_after,journal.transactionCurrency.code) }} + + {{ formatAnything(journal.transactionCurrency, transaction.source_account_before) }} + ⟶ {{ formatAnything(journal.transactionCurrency, transaction.source_account_after) }} </td> <td> {% if transaction.destination_account_type == 'Cash account' %} @@ -297,23 +298,26 @@ </td> <td> - {{ formatAmountWithCode(transaction.destination_account_before,journal.transactionCurrency.code) }} - ⟶ {{ formatAmountWithCode(transaction.destination_account_after,journal.transactionCurrency.code) }} + + {{ formatAnything(journal.transactionCurrency, transaction.destination_account_before) }} + ⟶ {{ formatAnything(journal.transactionCurrency, transaction.destination_account_after) }} </td> <td> {% if journal.transactiontype.type == 'Deposit' %} <!-- deposit, positive amount with correct currency --> - {{ formatAmountWithCode(transaction.destination_amount, journal.transactionCurrency.code) }} + {{ formatAnything(journal.transactionCurrency, transaction.destination_amount) }} {% endif %} {% if journal.transactiontype.type == 'Withdrawal' %} <!-- withdrawal, negative amount with correct currency --> - {{ formatAmountWithCode(transaction.source_amount, journal.transactionCurrency.code) }} + {{ formatAnything(journal.transactionCurrency, transaction.source_amount) }} {% endif %} {% if journal.transactiontype.type == 'Transfer' %} <!-- transfer, positive amount in blue --> - <span class="text-info">{{ formatAmountPlainWithCode(transaction.destination_amount, journal.transactionCurrency.code) }}</span> + <span class="text-info"> + {{ formatAnythingPlain(journal.transactionCurrency, transaction.destination_amount) }} + </span> {% endif %} </td> <td> diff --git a/routes/web.php b/routes/web.php index e053bf6acd..8b50c86560 100755 --- a/routes/web.php +++ b/routes/web.php @@ -49,6 +49,7 @@ Route::group( /** * For the two factor routes, the user must be logged in, but NOT 2FA. Account confirmation does not matter here. + * @deprecated */ Route::group( ['middleware' => 'user-logged-in-no-2fa', 'prefix' => 'two-factor', 'as' => 'two-factor.', 'namespace' => 'Auth'], function () { @@ -59,18 +60,6 @@ Route::group( } ); -/** - * For the confirmation routes, the user must be logged in, also 2FA, but his account must not be confirmed. - */ -Route::group( - ['middleware' => 'user-logged-in-2fa-no-activation', 'namespace' => 'Auth'], function () { - Route::get('/confirm-your-account', ['uses' => 'ConfirmationController@confirmationError', 'as' => 'confirmation_error']); - Route::get('/resend-confirmation', ['uses' => 'ConfirmationController@resendConfirmation', 'as' => 'resend_confirmation']); - Route::get('/confirmation/{code}', ['uses' => 'ConfirmationController@doConfirmation', 'as' => 'do_confirm_account']); - -} -); - /** * For all other routes, the user must be fully authenticated and have an activated account. */ @@ -155,7 +144,7 @@ Route::group( Route::get('edit/{budget}', ['uses' => 'BudgetController@edit', 'as' => 'edit']); Route::get('delete/{budget}', ['uses' => 'BudgetController@delete', 'as' => 'delete']); Route::get('show/{budget}', ['uses' => 'BudgetController@show', 'as' => 'show']); - Route::get('show/{budget}/{limitrepetition}', ['uses' => 'BudgetController@showByRepetition', 'as' => 'show.repetition']); + Route::get('show/{budget}/{budgetlimit}', ['uses' => 'BudgetController@showByBudgetLimit', 'as' => 'show.limit']); Route::get('list/no-budget', ['uses' => 'BudgetController@noBudget', 'as' => 'no-budget']); Route::post('income', ['uses' => 'BudgetController@postUpdateIncome', 'as' => 'income.post']); @@ -177,6 +166,7 @@ Route::group( Route::get('delete/{category}', ['uses' => 'CategoryController@delete', 'as' => 'delete']); Route::get('show/{category}', ['uses' => 'CategoryController@show', 'as' => 'show']); + Route::get('show/{category}/all', ['uses' => 'CategoryController@showAll', 'as' => 'show.all']); Route::get('show/{category}/{date}', ['uses' => 'CategoryController@showByDate', 'as' => 'show.date']); Route::get('list/no-category', ['uses' => 'CategoryController@noCategory', 'as' => 'no-category']); @@ -260,7 +250,7 @@ Route::group( Route::get('frontpage', ['uses' => 'BudgetController@frontpage', 'as' => 'frontpage']); Route::get('period/0/{accountList}/{start_date}/{end_date}', ['uses' => 'BudgetController@periodNoBudget', 'as' => 'period.no-budget']); Route::get('period/{budget}/{accountList}/{start_date}/{end_date}', ['uses' => 'BudgetController@period', 'as' => 'period']); - Route::get('budget/{budget}/{limitrepetition}', ['uses' => 'BudgetController@budgetLimit', 'as' => 'budget-limit']); + Route::get('budget/{budget}/{budgetlimit}', ['uses' => 'BudgetController@budgetLimit', 'as' => 'budget-limit']); Route::get('budget/{budget}', ['uses' => 'BudgetController@budget', 'as' => 'budget']); @@ -694,11 +684,6 @@ Route::group( Route::get('users/show/{user}', ['uses' => 'UserController@show', 'as' => 'users.show']); Route::post('users/update/{user}', ['uses' => 'UserController@update', 'as' => 'users.update']); - // user domain manager - Route::get('domains', ['uses' => 'DomainController@domains', 'as' => 'users.domains']); - Route::get('domains/toggle/{domain}', ['uses' => 'DomainController@toggleDomain', 'as' => 'users.domains.block-toggle']); - Route::post('domains/manual', ['uses' => 'DomainController@manual', 'as' => 'users.domains.manual']); - // FF configuration: Route::get('configuration', ['uses' => 'ConfigurationController@index', 'as' => 'configuration.index']); Route::post('configuration', ['uses' => 'ConfigurationController@postIndex', 'as' => 'configuration.index.post']); diff --git a/storage/database/databasecopy.sqlite b/storage/database/databasecopy.sqlite index 41f250808b..03d527000f 100644 Binary files a/storage/database/databasecopy.sqlite and b/storage/database/databasecopy.sqlite differ diff --git a/test.sh b/test.sh index befdb1c714..3c06c5acec 100755 --- a/test.sh +++ b/test.sh @@ -81,6 +81,7 @@ then echo "Will not reset database" fi +echo "Copy test database over original" # take database from copy: cp $DATABASECOPY $DATABASE diff --git a/tests/acceptance/Controllers/AccountControllerTest.php b/tests/acceptance/Controllers/AccountControllerTest.php index fb03855828..e42a5a8d1a 100644 --- a/tests/acceptance/Controllers/AccountControllerTest.php +++ b/tests/acceptance/Controllers/AccountControllerTest.php @@ -8,11 +8,13 @@ * * See the LICENSE file for details. */ +use Carbon\Carbon; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Account; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountTaskerInterface; use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; /** @@ -60,15 +62,16 @@ class AccountControllerTest extends TestCase */ public function testDestroy() { + $this->session(['accounts.delete.url' => 'http://localhost/accounts/show/1']); + $repository = $this->mock(AccountRepositoryInterface::class); $repository->shouldReceive('find')->withArgs([0])->once()->andReturn(new Account); $repository->shouldReceive('destroy')->andReturn(true); - $this->session(['accounts.delete.url' => 'http://localhost']); + $this->be($this->user()); $this->call('post', route('accounts.destroy', [1])); $this->assertResponseStatus(302); $this->assertSessionHas('success'); - } /** @@ -85,6 +88,7 @@ class AccountControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\AccountController::index + * @covers FireflyIII\Http\Controllers\AccountController::isInArray * @dataProvider dateRangeProvider * * @param string $range @@ -101,17 +105,26 @@ class AccountControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\AccountController::show + * @covers FireflyIII\Http\Controllers\AccountController::periodEntries * @dataProvider dateRangeProvider * * @param string $range */ public function testShow(string $range) { + $date = new Carbon; + $this->session(['start' => $date, 'end' => clone $date]); $tasker = $this->mock(AccountTaskerInterface::class); $tasker->shouldReceive('amountOutInPeriod')->withAnyArgs()->andReturn('-1'); $tasker->shouldReceive('amountInInPeriod')->withAnyArgs()->andReturn('1'); + // mock repository: + $repository = $this->mock(AccountRepositoryInterface::class); + $repository->shouldReceive('oldestJournalDate')->andReturn(clone $date); + $repository->shouldReceive('getAccountsByType')->andReturn(new Collection); + + $collector = $this->mock(JournalCollectorInterface::class); $collector->shouldReceive('setAccounts')->andReturnSelf(); $collector->shouldReceive('setRange')->andReturnSelf(); @@ -129,7 +142,23 @@ class AccountControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\AccountController::showWithDate + * @covers FireflyIII\Http\Controllers\AccountController::showAll + * @dataProvider dateRangeProvider + * + * @param string $range + */ + public function testShowAll(string $range) + { + $this->be($this->user()); + $this->changeDateRange($this->user(), $range); + $this->call('GET', route('accounts.show.all', [1])); + $this->assertResponseStatus(200); + // has bread crumb + $this->see('<ol class="breadcrumb">'); + } + + /** + * @covers FireflyIII\Http\Controllers\AccountController::showByDate * @dataProvider dateRangeProvider * * @param string $range diff --git a/tests/acceptance/Controllers/Admin/ConfigurationControllerTest.php b/tests/acceptance/Controllers/Admin/ConfigurationControllerTest.php index 6c3e6f4f44..b212b656fd 100644 --- a/tests/acceptance/Controllers/Admin/ConfigurationControllerTest.php +++ b/tests/acceptance/Controllers/Admin/ConfigurationControllerTest.php @@ -30,7 +30,6 @@ class ConfigurationControllerTest extends TestCase { parent::setUp(); - FireflyConfig::shouldReceive('get')->withArgs(['must_confirm_account', false])->once(); } /** @@ -47,16 +46,8 @@ class ConfigurationControllerTest extends TestCase $trueConfig->data = true; FireflyConfig::shouldReceive('get')->withArgs(['single_user_mode', true])->once()->andReturn($trueConfig); - FireflyConfig::shouldReceive('get')->withArgs(['must_confirm_account', false])->once()->andReturn($falseConfig); FireflyConfig::shouldReceive('get')->withArgs(['is_demo_site', false])->times(2)->andReturn($falseConfig); - // new settings: - FireflyConfig::shouldReceive('get')->withArgs(['mail_for_lockout', false])->once()->andReturn($falseConfig); - FireflyConfig::shouldReceive('get')->withArgs(['mail_for_blocked_domain', false])->once()->andReturn($falseConfig); - FireflyConfig::shouldReceive('get')->withArgs(['mail_for_blocked_email', false])->once()->andReturn($falseConfig); - FireflyConfig::shouldReceive('get')->withArgs(['mail_for_bad_login', false])->once()->andReturn($falseConfig); - FireflyConfig::shouldReceive('get')->withArgs(['mail_for_blocked_login', false])->once()->andReturn($falseConfig); - $this->call('GET', route('admin.configuration.index')); $this->assertResponseStatus(200); @@ -65,7 +56,7 @@ class ConfigurationControllerTest extends TestCase } /** - * @covers \FireflyIII\Http\Controllers\Admin\ConfigurationController::store + * @covers \FireflyIII\Http\Controllers\Admin\ConfigurationController::postIndex */ public function testPostIndex() { @@ -74,13 +65,7 @@ class ConfigurationControllerTest extends TestCase FireflyConfig::shouldReceive('get')->withArgs(['is_demo_site', false])->once()->andReturn($falseConfig); FireflyConfig::shouldReceive('set')->withArgs(['single_user_mode', false])->once(); - FireflyConfig::shouldReceive('set')->withArgs(['must_confirm_account', false])->once(); FireflyConfig::shouldReceive('set')->withArgs(['is_demo_site', false])->once(); - FireflyConfig::shouldReceive('set')->withArgs(['mail_for_lockout', false])->once(); - FireflyConfig::shouldReceive('set')->withArgs(['mail_for_blocked_domain', false])->once(); - FireflyConfig::shouldReceive('set')->withArgs(['mail_for_blocked_email', false])->once(); - FireflyConfig::shouldReceive('set')->withArgs(['mail_for_bad_login', false])->once(); - FireflyConfig::shouldReceive('set')->withArgs(['mail_for_blocked_login', false])->once(); $this->be($this->user()); $this->call('POST', route('admin.configuration.index.post')); diff --git a/tests/acceptance/Controllers/Admin/DomainControllerTest.php b/tests/acceptance/Controllers/Admin/DomainControllerTest.php deleted file mode 100644 index bba83a9265..0000000000 --- a/tests/acceptance/Controllers/Admin/DomainControllerTest.php +++ /dev/null @@ -1,68 +0,0 @@ -<?php -/** - * DomainControllerTest.php - * Copyright (C) 2016 thegrumpydictator@gmail.com - * - * This software may be modified and distributed under the terms of the - * Creative Commons Attribution-ShareAlike 4.0 International License. - * - * See the LICENSE file for details. - */ - -namespace Admin; - -use TestCase; - -/** - * Generated by PHPUnit_SkeletonGenerator on 2016-12-07 at 18:50:31. - */ -class DomainControllerTest extends TestCase -{ - - - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - public function setUp() - { - parent::setUp(); - } - - /** - * @covers \FireflyIII\Http\Controllers\Admin\DomainController::domains - */ - public function testDomains() - { - - $this->be($this->user()); - $this->call('GET', route('admin.users.domains')); - $this->assertResponseStatus(200); - - // has bread crumb - $this->see('<ol class="breadcrumb">'); - } - - /** - * @covers \FireflyIII\Http\Controllers\Admin\DomainController::manual - */ - public function testManual() - { - $this->be($this->user()); - $this->call('POST', route('admin.users.domains.manual'), ['domain' => 'example2.com']); - $this->assertSessionHas('success'); - $this->assertResponseStatus(302); - } - - /** - * @covers \FireflyIII\Http\Controllers\Admin\DomainController::toggleDomain - */ - public function testToggleDomain() - { - $this->be($this->user()); - $this->call('GET', route('admin.users.domains.block-toggle', ['example.com'])); - $this->assertSessionHas('message'); - $this->assertResponseStatus(302); - } - -} diff --git a/tests/acceptance/Controllers/AttachmentControllerTest.php b/tests/acceptance/Controllers/AttachmentControllerTest.php index be401ef04d..a2fdbc3d07 100644 --- a/tests/acceptance/Controllers/AttachmentControllerTest.php +++ b/tests/acceptance/Controllers/AttachmentControllerTest.php @@ -45,6 +45,7 @@ class AttachmentControllerTest extends TestCase public function testDestroy() { $this->session(['attachments.delete.url' => 'http://localhost']); + $repository = $this->mock(AttachmentRepositoryInterface::class); $repository->shouldReceive('destroy')->andReturn(true); $this->be($this->user()); diff --git a/tests/acceptance/Controllers/Auth/ConfirmationControllerTest.php b/tests/acceptance/Controllers/Auth/ConfirmationControllerTest.php deleted file mode 100644 index c1e5cfa0cb..0000000000 --- a/tests/acceptance/Controllers/Auth/ConfirmationControllerTest.php +++ /dev/null @@ -1,121 +0,0 @@ -<?php -/** - * ConfirmationControllerTest.php - * Copyright (C) 2016 thegrumpydictator@gmail.com - * - * This software may be modified and distributed under the terms of the - * Creative Commons Attribution-ShareAlike 4.0 International License. - * - * See the LICENSE file for details. - */ - -namespace Auth; - -use FireflyIII\Models\Configuration; -use FireflyIII\Models\Preference; -use FireflyIII\Support\Facades\FireflyConfig; -use FireflyIII\Support\Facades\Preferences; -use TestCase; - -/** - * Generated by PHPUnit_SkeletonGenerator on 2016-12-07 at 18:50:31. - */ -class ConfirmationControllerTest extends TestCase -{ - - - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - public function setUp() - { - parent::setUp(); - } - - /** - * @covers \FireflyIII\Http\Controllers\Auth\ConfirmationController::confirmationError - */ - public function testConfirmationError() - { - // need a user that is not activated. And site must require activated users. - $trueConfig = new Configuration; - $trueConfig->data = true; - $falsePreference = new Preference; - $falsePreference->data = false; - - Preferences::shouldReceive('get')->withArgs(['user_confirmed', false])->andReturn($falsePreference); - Preferences::shouldReceive('get')->withArgs(['twoFactorAuthEnabled', false])->andReturn($falsePreference); - Preferences::shouldReceive('get')->withArgs(['twoFactorAuthSecret'])->andReturn(null); - - $falseConfig = new Configuration; - $falseConfig->data = false; - - FireflyConfig::shouldReceive('get')->withArgs(['is_demo_site', false])->once()->andReturn($falseConfig); - - FireflyConfig::shouldReceive('get')->withArgs(['must_confirm_account', false])->andReturn($trueConfig); - $this->be($this->user()); - $this->call('GET', route('confirmation_error')); - $this->assertResponseStatus(200); - $this->see('has been sent to the address you used during your registration'); - - } - - /** - * @covers \FireflyIII\Http\Controllers\Auth\ConfirmationController::doConfirmation - */ - public function testDoConfirmation() - { - $codePreference = new Preference; - $codePreference->data = 'abcde'; - $timePreference = new Preference; - $timePreference->data = 0; - $falsePreference = new Preference; - $falsePreference->data = false; - - Preferences::shouldReceive('get')->withArgs(['user_confirmed_code'])->andReturn($codePreference); - Preferences::shouldReceive('get')->withArgs(['user_confirmed_last_mail', 0])->andReturn($timePreference); - Preferences::shouldReceive('get')->withArgs(['twoFactorAuthEnabled', false])->andReturn($falsePreference); - Preferences::shouldReceive('get')->withArgs(['twoFactorAuthSecret'])->andReturn(null); - Preferences::shouldReceive('get')->withArgs(['user_confirmed', false])->andReturn($falsePreference); - - $this->be($this->user()); - $this->call('GET', route('do_confirm_account', ['abcde'])); - $this->assertResponseStatus(302); - $this->assertRedirectedToRoute('home'); - } - - /** - * @covers \FireflyIII\Http\Controllers\Auth\ConfirmationController::resendConfirmation - */ - public function testResendConfirmation() - { - $trueConfig = new Configuration; - $trueConfig->data = true; - $codePreference = new Preference; - $codePreference->data = 'abcde'; - $timePreference = new Preference; - $timePreference->data = 0; - $falsePreference = new Preference; - $falsePreference->data = false; - - $falseConfig = new Configuration; - $falseConfig->data = false; - - FireflyConfig::shouldReceive('get')->withArgs(['is_demo_site', false])->once()->andReturn($falseConfig); - - Preferences::shouldReceive('get')->withArgs(['user_confirmed_last_mail', 0])->andReturn($timePreference); - Preferences::shouldReceive('get')->withArgs(['twoFactorAuthEnabled', false])->andReturn($falsePreference); - Preferences::shouldReceive('get')->withArgs(['twoFactorAuthSecret'])->andReturn(null); - FireflyConfig::shouldReceive('get')->withArgs(['must_confirm_account', false])->andReturn($trueConfig); - Preferences::shouldReceive('get')->withArgs(['user_confirmed', false])->andReturn($falsePreference); - - // from event handler: - Preferences::shouldReceive('setForUser')->withAnyArgs(); - - $this->be($this->user()); - $this->call('GET', route('resend_confirmation')); - $this->assertResponseStatus(200); - } - -} diff --git a/tests/acceptance/Controllers/BudgetControllerTest.php b/tests/acceptance/Controllers/BudgetControllerTest.php index cef5c1cb2e..2057ce68f8 100644 --- a/tests/acceptance/Controllers/BudgetControllerTest.php +++ b/tests/acceptance/Controllers/BudgetControllerTest.php @@ -8,7 +8,12 @@ * * See the LICENSE file for details. */ +use Carbon\Carbon; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; /** @@ -70,6 +75,7 @@ class BudgetControllerTest extends TestCase public function testDestroy() { $this->session(['budgets.delete.url' => 'http://localhost']); + $repository = $this->mock(BudgetRepositoryInterface::class); $repository->shouldReceive('destroy')->andReturn(true); @@ -99,6 +105,13 @@ class BudgetControllerTest extends TestCase */ public function testIndex(string $range) { + $repository = $this->mock(BudgetRepositoryInterface::class); + $repository->shouldReceive('cleanupBudgets'); + $repository->shouldReceive('getActiveBudgets')->andReturn(new Collection); + $repository->shouldReceive('getInactiveBudgets')->andReturn(new Collection); + $repository->shouldReceive('getAvailableBudget')->andReturn('100.123'); + + $this->be($this->user()); $this->changeDateRange($this->user(), $range); $this->call('GET', route('budgets.index')); @@ -115,6 +128,18 @@ class BudgetControllerTest extends TestCase */ public function testNoBudget(string $range) { + $date = new Carbon(); + $this->session(['start' => $date, 'end' => clone $date]); + + $collector = $this->mock(JournalCollectorInterface::class); + $collector->shouldReceive('setAllAssetAccounts')->andReturnSelf(); + $collector->shouldReceive('setRange')->andReturnSelf(); + $collector->shouldReceive('setLimit')->andReturnSelf(); + $collector->shouldReceive('setPage')->andReturnSelf(); + $collector->shouldReceive('withoutBudget')->andReturnSelf(); + $collector->shouldReceive('withCategoryInformation')->andReturnSelf(); + $collector->shouldReceive('getPaginatedJournals')->andReturn(new LengthAwarePaginator([], 0, 10)); + $this->be($this->user()); $this->changeDateRange($this->user(), $range); $this->call('GET', route('budgets.no-budget')); @@ -129,7 +154,7 @@ class BudgetControllerTest extends TestCase public function testPostUpdateIncome() { $data = [ - 'amount' => '200', + 'amount' => '200', ]; $this->be($this->user()); $this->call('post', route('budgets.income.post'), $data); @@ -144,6 +169,30 @@ class BudgetControllerTest extends TestCase */ public function testShow(string $range) { + $date = new Carbon(); + $date->subDay(); + $this->session(['first' => $date]); + + // mock account repository + $accountRepository = $this->mock(AccountRepositoryInterface::class); + $accountRepository->shouldReceive('getAccountsByType')->andReturn(new Collection); + + + // mock budget repository + $budgetRepository = $this->mock(BudgetRepositoryInterface::class); + $budgetRepository->shouldReceive('getBudgetLimits')->andReturn(new Collection); + $budgetRepository->shouldReceive('spentInPeriod')->andReturn('1'); + // mock journal collector: + $collector = $this->mock(JournalCollectorInterface::class); + $collector->shouldReceive('setAllAssetAccounts')->andReturnSelf(); + $collector->shouldReceive('setRange')->andReturnSelf(); + $collector->shouldReceive('setLimit')->andReturnSelf(); + $collector->shouldReceive('setPage')->andReturnSelf(); + $collector->shouldReceive('setBudget')->andReturnSelf(); + $collector->shouldReceive('withCategoryInformation')->andReturnSelf(); + $collector->shouldReceive('getPaginatedJournals')->andReturn(new LengthAwarePaginator([], 0, 10)); + + $this->be($this->user()); $this->changeDateRange($this->user(), $range); $this->call('GET', route('budgets.show', [1])); @@ -152,16 +201,35 @@ class BudgetControllerTest extends TestCase } /** - * @covers \FireflyIII\Http\Controllers\BudgetController::showByRepetition + * @covers \FireflyIII\Http\Controllers\BudgetController::showByBudgetLimit() * @dataProvider dateRangeProvider * * @param string $range */ - public function testShowByRepetition(string $range) + public function testShowByBudgetLimit(string $range) { + // mock account repository + $accountRepository = $this->mock(AccountRepositoryInterface::class); + $accountRepository->shouldReceive('getAccountsByType')->andReturn(new Collection); + + // mock budget repository + $budgetRepository = $this->mock(BudgetRepositoryInterface::class); + $budgetRepository->shouldReceive('spentInPeriod')->andReturn('1'); + + // mock journal collector: + $collector = $this->mock(JournalCollectorInterface::class); + $collector->shouldReceive('setAllAssetAccounts')->andReturnSelf(); + $collector->shouldReceive('setRange')->andReturnSelf(); + $collector->shouldReceive('setLimit')->andReturnSelf(); + $collector->shouldReceive('setPage')->andReturnSelf(); + $collector->shouldReceive('setBudget')->andReturnSelf(); + $collector->shouldReceive('withCategoryInformation')->andReturnSelf(); + $collector->shouldReceive('getPaginatedJournals')->andReturn(new LengthAwarePaginator([], 0, 10)); + + $this->be($this->user()); $this->changeDateRange($this->user(), $range); - $this->call('GET', route('budgets.show.repetition', [1, 1])); + $this->call('GET', route('budgets.show.limit', [1, 1])); $this->assertResponseStatus(200); // has bread crumb $this->see('<ol class="breadcrumb">'); @@ -181,14 +249,6 @@ class BudgetControllerTest extends TestCase $this->call('post', route('budgets.store'), $data); $this->assertResponseStatus(302); $this->assertSessionHas('success'); - - // must be in list - $this->be($this->user()); - $this->call('GET', route('budgets.index')); - $this->assertResponseStatus(200); - $this->see('<ol class="breadcrumb">'); - $this->see($data['name']); - } /** @@ -206,14 +266,7 @@ class BudgetControllerTest extends TestCase $this->call('post', route('budgets.update', [1]), $data); $this->assertResponseStatus(302); $this->assertSessionHas('success'); - - // must be in list - $this->be($this->user()); - $this->call('GET', route('budgets.index')); - $this->assertResponseStatus(200); - $this->see('<ol class="breadcrumb">'); - $this->see($data['name']); - } + } /** * @covers \FireflyIII\Http\Controllers\BudgetController::updateIncome diff --git a/tests/acceptance/Controllers/CategoryControllerTest.php b/tests/acceptance/Controllers/CategoryControllerTest.php index 856f6b44c8..3b546635d3 100644 --- a/tests/acceptance/Controllers/CategoryControllerTest.php +++ b/tests/acceptance/Controllers/CategoryControllerTest.php @@ -132,6 +132,7 @@ class CategoryControllerTest extends TestCase $collector->shouldReceive('setLimit')->andReturnSelf()->once(); $collector->shouldReceive('setAllAssetAccounts')->andReturnSelf()->once(); $collector->shouldReceive('setRange')->andReturnSelf()->once(); + $collector->shouldReceive('withBudgetInformation')->andReturnSelf()->once(); $collector->shouldReceive('setCategory')->andReturnSelf()->once(); $collector->shouldReceive('getPaginatedJournals')->andReturn(new LengthAwarePaginator([], 0, 10))->once(); @@ -147,6 +148,32 @@ class CategoryControllerTest extends TestCase $this->see('<ol class="breadcrumb">'); } + /** + * @covers \FireflyIII\Http\Controllers\CategoryController::showAll + * @dataProvider dateRangeProvider + * + * @param string $range + */ + public function testShowAll(string $range) + { + $collector = $this->mock(JournalCollectorInterface::class); + + // collector stuff: + $collector->shouldReceive('setPage')->andReturnSelf()->once(); + $collector->shouldReceive('setLimit')->andReturnSelf()->once(); + $collector->shouldReceive('setAllAssetAccounts')->andReturnSelf()->once(); + $collector->shouldReceive('withBudgetInformation')->andReturnSelf()->once(); + $collector->shouldReceive('setCategory')->andReturnSelf()->once(); + $collector->shouldReceive('getPaginatedJournals')->andReturn(new LengthAwarePaginator([], 0, 10))->once(); + + + $this->be($this->user()); + $this->changeDateRange($this->user(), $range); + $this->call('GET', route('categories.show.all', [1])); + $this->assertResponseStatus(200); + $this->see('<ol class="breadcrumb">'); + } + /** * @covers \FireflyIII\Http\Controllers\CategoryController::showByDate * @dataProvider dateRangeProvider @@ -162,6 +189,7 @@ class CategoryControllerTest extends TestCase $collector->shouldReceive('setLimit')->andReturnSelf()->once(); $collector->shouldReceive('setAllAssetAccounts')->andReturnSelf()->once(); $collector->shouldReceive('setRange')->andReturnSelf()->once(); + $collector->shouldReceive('withBudgetInformation')->andReturnSelf()->once(); $collector->shouldReceive('setCategory')->andReturnSelf()->once(); $collector->shouldReceive('getPaginatedJournals')->andReturn(new LengthAwarePaginator([], 0, 10))->once(); diff --git a/tests/acceptance/Controllers/CurrencyControllerTest.php b/tests/acceptance/Controllers/CurrencyControllerTest.php index 3ee0593a90..36d14bacde 100644 --- a/tests/acceptance/Controllers/CurrencyControllerTest.php +++ b/tests/acceptance/Controllers/CurrencyControllerTest.php @@ -109,9 +109,10 @@ class CurrencyControllerTest extends TestCase { $this->session(['currencies.create.url' => 'http://localhost']); $data = [ - 'name' => 'XX', - 'code' => 'XXX', - 'symbol' => 'x', + 'name' => 'XX', + 'code' => 'XXX', + 'symbol' => 'x', + 'decimal_places' => 2, ]; $this->be($this->user()); $this->call('post', route('currencies.store'), $data); @@ -126,9 +127,10 @@ class CurrencyControllerTest extends TestCase { $this->session(['currencies.edit.url' => 'http://localhost']); $data = [ - 'name' => 'XA', - 'code' => 'XAX', - 'symbol' => 'a', + 'name' => 'XA', + 'code' => 'XAX', + 'symbol' => 'a', + 'decimal_places' => 2, ]; $this->be($this->user()); $this->call('post', route('currencies.update', [2]), $data); diff --git a/tests/acceptance/Controllers/ExportControllerTest.php b/tests/acceptance/Controllers/ExportControllerTest.php index ee94794c22..39a7bd4ebc 100644 --- a/tests/acceptance/Controllers/ExportControllerTest.php +++ b/tests/acceptance/Controllers/ExportControllerTest.php @@ -8,8 +8,13 @@ * * See the LICENSE file for details. */ +use Carbon\Carbon; use FireflyIII\Export\Processor; +use FireflyIII\Export\ProcessorInterface; +use FireflyIII\Models\ExportJob; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface; +use Illuminate\Support\Collection; /** * Generated by PHPUnit_SkeletonGenerator on 2016-12-10 at 05:51:41. @@ -69,6 +74,11 @@ class ExportControllerTest extends TestCase */ public function testPostIndex() { + $this->session( + ['first' => new Carbon('2014-01-01')] + ); + + $data = [ 'export_start_range' => '2015-01-01', @@ -77,14 +87,22 @@ class ExportControllerTest extends TestCase 'accounts' => [1], 'job' => 'testExport', ]; - $processor = $this->mock(Processor::class); + + $accountRepository = $this->mock(AccountRepositoryInterface::class); + $accountRepository->shouldReceive('getAccountsById')->withArgs([$data['accounts']])->andReturn(new Collection); + + $processor = $this->mock(ProcessorInterface::class); $processor->shouldReceive('collectJournals')->once(); $processor->shouldReceive('convertJournals')->once(); $processor->shouldReceive('exportJournals')->once(); $processor->shouldReceive('createZipFile')->once(); + $repository = $this->mock(ExportJobRepositoryInterface::class); + $repository->shouldReceive('changeStatus')->andReturn(true); + $repository->shouldReceive('findByKey')->andReturn(new ExportJob); $this->be($this->user()); + $this->call('post', route('export.export'), $data); $this->assertResponseStatus(200); $this->see('ok'); diff --git a/tests/acceptance/Controllers/PiggyBankControllerTest.php b/tests/acceptance/Controllers/PiggyBankControllerTest.php index 66e2dce67c..d480600d21 100644 --- a/tests/acceptance/Controllers/PiggyBankControllerTest.php +++ b/tests/acceptance/Controllers/PiggyBankControllerTest.php @@ -8,6 +8,7 @@ * * See the LICENSE file for details. */ +use FireflyIII\Models\PiggyBank; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; @@ -123,7 +124,7 @@ class PiggyBankControllerTest extends TestCase */ public function testPostAdd() { - $data = ['amount' => 1]; + $data = ['amount' => '1.123']; $this->be($this->user()); $this->call('post', route('piggy-banks.add', [1]), $data); $this->assertResponseStatus(302); @@ -131,12 +132,33 @@ class PiggyBankControllerTest extends TestCase $this->assertSessionHas('success'); } + /** + * Add the exact amount to fill a piggy bank + * + * @covers \FireflyIII\Http\Controllers\PiggyBankController::postAdd + */ + public function testPostAddExact() + { + // find a piggy with current amount = 0. + $piggy = PiggyBank::leftJoin('piggy_bank_repetitions', 'piggy_bank_repetitions.piggy_bank_id', '=', 'piggy_banks.id') + ->where('currentamount', 0) + ->first(['piggy_banks.id', 'targetamount']); + + + $data = ['amount' => strval($piggy->targetamount)]; + $this->be($this->user()); + $this->call('post', route('piggy-banks.add', [$piggy->id]), $data); + $this->assertResponseStatus(302); + $this->assertRedirectedToRoute('piggy-banks.index'); + $this->assertSessionHas('success'); + } + /** * @covers \FireflyIII\Http\Controllers\PiggyBankController::postRemove */ public function testPostRemove() { - $data = ['amount' => 1]; + $data = ['amount' => '1.123']; $this->be($this->user()); $this->call('post', route('piggy-banks.remove', [1]), $data); $this->assertResponseStatus(302); @@ -184,7 +206,7 @@ class PiggyBankControllerTest extends TestCase $this->session(['piggy-banks.create.url' => 'http://localhost']); $data = [ 'name' => 'Piggy ' . rand(999, 10000), - 'targetamount' => 100, + 'targetamount' => '100.123', 'account_id' => 2, 'amount_currency_id_targetamount' => 1, @@ -204,7 +226,7 @@ class PiggyBankControllerTest extends TestCase $this->session(['piggy-banks.edit.url' => 'http://localhost']); $data = [ 'name' => 'Updated Piggy ' . rand(999, 10000), - 'targetamount' => 100, + 'targetamount' => '100.123', 'account_id' => 2, 'amount_currency_id_targetamount' => 1, diff --git a/tests/unit/Models/AccountTest.php b/tests/unit/Models/AccountTest.php new file mode 100644 index 0000000000..6be272929b --- /dev/null +++ b/tests/unit/Models/AccountTest.php @@ -0,0 +1,83 @@ +<?php +/** + * AccountTest.php + * Copyright (c) 2016 thegrumpydictator@gmail.com + * This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License. + * + * See the LICENSE file for details. + */ + +declare(strict_types = 1); + +use FireflyIII\Models\Account; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +class AccountTest extends TestCase +{ + + /** + * @covers \FireflyIII\Models\Account::firstOrCreateEncrypted + */ + public function testEncrypted() + { + $data = [ + 'user_id' => 1, + 'name' => 'Test account #' . rand(1000, 9999), + ]; + $account = Account::firstOrCreateEncrypted($data); + + $this->assertEquals($account->user_id, $data['user_id']); + $this->assertEquals($account->name, $data['name']); + } + + /** + * @covers \FireflyIII\Models\Account::firstOrCreateEncrypted + */ + public function testEncryptedIban() + { + $data = [ + 'user_id' => 1, + 'iban' => 'NL64RABO0133183395', + ]; + $account = Account::firstOrCreateEncrypted($data); + + $this->assertEquals($account->user_id, $data['user_id']); + $this->assertEquals($account->name, $data['iban']); + } + + /** + * @covers \FireflyIII\Models\Account::firstOrCreateEncrypted + * @expectedException \FireflyIII\Exceptions\FireflyException + */ + public function testEncryptedNoId() + { + $data = [ + 'name' => 'Test account', + ]; + $account = Account::firstOrCreateEncrypted($data); + } + + /** + * @covers \FireflyIII\Models\Account::routeBinder + */ + public function testRouteBinder() + { + // not logged in? + $this->be($this->user()); + $this->call('get', route('accounts.show', [1])); + + } + + /** + * One that belongs to another user. + * + * @covers \FireflyIII\Models\Account::routeBinder + */ + public function testRouteBinderError() + { + $account = Account::whereUserId(3)->first(); + $this->be($this->user()); + $this->call('get', route('accounts.show', [$account->id])); + $this->assertResponseStatus(404); + } +} \ No newline at end of file diff --git a/tests/unit/Models/TransactionTypeTest.php b/tests/unit/Models/TransactionTypeTest.php index e0c3f2aa86..51d8a1ea76 100644 --- a/tests/unit/Models/TransactionTypeTest.php +++ b/tests/unit/Models/TransactionTypeTest.php @@ -17,24 +17,37 @@ use FireflyIII\Models\TransactionType; */ class TransactionTypeTest extends TestCase { + /** + * @covers \FireflyIII\Models\TransactionType::isDeposit + */ public function testIsDeposit() { + $transactionType = TransactionType::whereType(TransactionType::DEPOSIT)->first(); $this->assertTrue($transactionType->isDeposit()); } + /** + * @covers \FireflyIII\Models\TransactionType::isOpeningBalance + */ public function testIsOpeningBalance() { $transactionType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first(); $this->assertTrue($transactionType->isOpeningBalance()); } + /** + * @covers \FireflyIII\Models\TransactionType::isTransfer + */ public function testIsTransfer() { $transactionType = TransactionType::whereType(TransactionType::TRANSFER)->first(); $this->assertTrue($transactionType->isTransfer()); } + /** + * @covers \FireflyIII\Models\TransactionType::isWithdrawal + */ public function testIsWithdrawal() { $transactionType = TransactionType::whereType(TransactionType::WITHDRAWAL)->first();