Merge branch 'release/4.6.6'

This commit is contained in:
James Cole
2017-09-30 09:11:16 +02:00
389 changed files with 5848 additions and 6624 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
# Ignore composer specific files and vendor folder
composer.phar
composer.lock
vendor

View File

@@ -1,7 +1,6 @@
APP_ENV=${FF_APP_ENV}
APP_DEBUG=false
APP_FORCE_SSL=false
APP_FORCE_ROOT=
APP_NAME=FireflyIII
APP_KEY=${FF_APP_KEY}
APP_LOG=daily
APP_LOG_LEVEL=warning
@@ -28,7 +27,7 @@ REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_FROM=changeme@example.com
MAIL_USERNAME=null
@@ -41,6 +40,8 @@ SHOW_INCOMPLETE_TRANSLATIONS=false
CACHE_PREFIX=firefly
EXCHANGE_RATE_SERVICE=fixerio
GOOGLE_MAPS_API_KEY=
ANALYTICS_ID=
SITE_OWNER=mail@example.com
@@ -48,7 +49,7 @@ USE_ENCRYPTION=true
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_APP_ID=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=

7
.env.example Executable file → Normal file
View File

@@ -1,7 +1,6 @@
APP_ENV=production
APP_DEBUG=false
APP_FORCE_SSL=false
APP_FORCE_ROOT=
APP_NAME=FireflyIII
APP_KEY=SomeRandomStringOf32CharsExactly
APP_LOG=daily
APP_LOG_LEVEL=warning
@@ -28,7 +27,7 @@ REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_FROM=changeme@example.com
MAIL_USERNAME=null
@@ -50,7 +49,7 @@ USE_ENCRYPTION=true
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_APP_ID=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=

View File

@@ -1,7 +1,6 @@
APP_ENV=production
APP_DEBUG=true
APP_FORCE_SSL=false
APP_FORCE_ROOT=
APP_NAME=FireflyIII
APP_KEY=SomeRandomStringOf32CharsExactly
APP_LOG=syslog
APP_LOG_LEVEL=debug
@@ -28,7 +27,7 @@ REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_FROM=changeme@example.com
MAIL_USERNAME=null
@@ -41,6 +40,8 @@ SHOW_INCOMPLETE_TRANSLATIONS=false
CACHE_PREFIX=firefly
EXCHANGE_RATE_SERVICE=fixerio
GOOGLE_MAPS_API_KEY=
ANALYTICS_ID=
SITE_OWNER=mail@example.com
@@ -48,8 +49,7 @@ USE_ENCRYPTION=true
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_APP_ID=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=

21
.env.testing Executable file → Normal file
View File

@@ -1,7 +1,6 @@
APP_ENV=testing
APP_DEBUG=true
APP_FORCE_SSL=false
APP_FORCE_ROOT=
APP_NAME=FireflyIII
APP_KEY=TestTestTestTestTestTestTestTest
APP_LOG=daily
APP_LOG_LEVEL=debug
@@ -11,7 +10,7 @@ DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=homestead
DB_PASSWORD=secret
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
@@ -26,8 +25,8 @@ REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER=log
MAIL_HOST=mailtrap.io
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_FROM=changeme@example.com
MAIL_USERNAME=null
@@ -38,9 +37,19 @@ SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
SHOW_INCOMPLETE_TRANSLATIONS=false
CACHE_PREFIX=firefly
EXCHANGE_RATE_SERVICE=fixerio
GOOGLE_MAPS_API_KEY=
ANALYTICS_ID=
SITE_OWNER=mail@example.com
USE_ENCRYPTION=true
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_APP_ID=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=

2
.gitattributes vendored Executable file → Normal file
View File

@@ -1,3 +1,5 @@
* text=auto
*.css linguist-vendored
*.scss linguist-vendored
*.js linguist-vendored
CHANGELOG.md export-ignore

6
.gitignore vendored Executable file → Normal file
View File

@@ -1,8 +1,14 @@
/node_modules
/public/hot
/public/storage
/storage/*.key
/vendor
/.vagrant
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
.env
public/google*.html
report.html
composer.phar

View File

@@ -2,6 +2,45 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [4.6.6] - 2015-05-25
### Added
- #826, reported by @pkoziol.
- #855, by @ms32035
- #786, by @SmilingWorlock
- #875, by @gavu
- #834, by @gavu (and others)
### Changed
- Upgraded to Laravel 5.5
- Add version parameter to CSS and JS files
- #823, #824 fixed Docker config by @DieBauer
### Fixed
- #830
- #822, reported by @gazben
- #827, reported by @pkoziol
- #835, reported by @gavu
- #836, reported by @pkoziol
- #838, reported by @gavu
- #839, reported by @gavu
- #843, reported by @gavu
- #837, reported by @gavu
- #845, reported by @gavu
- #846, reported by @gavu
- #848, reported by @gavu
- #854, reported by @gavu
- #866, reported by @pkoziol
- #847, reported by @gavu
- #853, reported by @gavu
- #857, reported by @pkoziol
- #865, reported by @simonsmiley
- #826, reported by @pkoziol
- #856, reported by @ms32035
- #860, reported by @gavu
- #861, reported by @gavu
- #870, reported by @gavu
## [4.6.5] - 2017-09-09
### Added

View File

@@ -23,18 +23,31 @@ RUN docker-php-ext-install -j$(nproc) curl gd intl json mcrypt readline tidy zip
# Generate locales supported by firefly
RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
COPY docker/apache2.conf /etc/apache2/apache2.conf
COPY ./docker/apache2.conf /etc/apache2/apache2.conf
# Enable apache mod rewrite..
RUN a2enmod rewrite
# Enable apache mod ssl..
RUN a2enmod ssl
# Setup the Composer installer
run curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN cd /var/www && composer create-project grumpydictator/firefly-iii --no-dev --prefer-dist firefly-iii 4.6.4
COPY docker/entrypoint.sh /var/www/firefly-iii/docker/entrypoint.sh
ADD docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf
RUN chown -R www-data:www-data /var/www && chmod -R 775 /var/www/firefly-iii/storage
# Copy Apache Configs
COPY ./docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf
ENV FIREFLY_PATH /var/www/firefly-iii
WORKDIR $FIREFLY_PATH
# The working directory
COPY . $FIREFLY_PATH
RUN chown -R www-data:www-data /var/www && chmod -R 775 $FIREFLY_PATH/storage
RUN composer install --prefer-dist --no-dev --no-scripts
WORKDIR /var/www/firefly-iii
EXPOSE 80
ENTRYPOINT ["/var/www/firefly-iii/docker/entrypoint.sh"]
ENTRYPOINT ["docker/entrypoint.sh"]

View File

@@ -6,21 +6,31 @@
[![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)
"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.
"Firefly III" is a financial manager for your personal finances.
It can help you keep track of your expenses and income.
Firefly III supports the use of budgets. You can categorize and tag your transactions.
It also supports credit cards, shared household accounts and savings accounts.
There are many financial reports available.
## Try it out!
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/firefly-iii/firefly-iii/tree/master)
Firefly III can be run on Heroku. Register for a free Heroku account and instantly run Firefly III on your very own cloud instance. There is also a [demo site](https://firefly-iii.nder.be) with an example financial administration already present.
Firefly III can be run on Heroku.
Register for a free Heroku account and instantly run Firefly III on your very own cloud instance.
There is also a [demo site](https://firefly-iii.nder.be) with an example financial administration already present.
## Getting started
To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line. Then, please read the [installation guide](https://firefly-iii.github.io/using-installing.html).
To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line.
Then, please read the [installation guide](https://firefly-iii.github.io/using-installing.html).
At the moment, installation is fairly complex. I hope to improve this in the future. If you need support, please open a ticket.
## More about Firefly III
Personal financial management is pretty difficult, and everybody has their own approach to it. Some people make budgets, other people limit their cashflow by throwing away their credit cards, others try to increase their current cashflow. There are tons of ways to save and earn money.
Personal financial management is pretty difficult, and everybody has their own approach to it.
Some people make budgets, other people limit their cashflow by throwing away their credit cards,
others try to increase their current cashflow. There are tons of ways to save and earn money.
Firefly works on the principle that if you know where you're money is going, you can stop it from going there.
@@ -31,7 +41,8 @@ Firefly works on the principle that if you know where you're money is going, you
- Firefly has lots of features without being fancy or bloated.
- If you feel you're missing something you can just ask me and I'll add it!
Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/).
Firefly III has become pretty awesome over the years! (but please excuse me for bragging, it's just that I'm proud of it).
[You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/).
### Contributing

View File

@@ -0,0 +1,143 @@
<?php
/**
* CreateExport.php
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Console\Commands;
use Carbon\Carbon;
use FireflyIII\Export\ProcessorInterface;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Console\Command;
use Storage;
/**
* Class CreateExport
*
* Generates export from the command line.
*
* @package FireflyIII\Console\Commands
*/
class CreateExport extends Command
{
use VerifiesAccessToken;
/**
* The console command description.
*
* @var string
*/
protected $description = 'Use this command to create a new import. Your user ID can be found on the /profile page.';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature
= 'firefly:create-export
{--user= : The user ID that the import should import for.}
{--token= : The user\'s access token.}
{--with_attachments : Include user\'s attachments?}
{--with_uploads : Include user\'s uploads?}';
/**
* Create a new command instance.
*
*/
public function __construct()
{
parent::__construct();
}
/**
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five its fine.
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if (!$this->verifyAccessToken()) {
$this->error('Invalid access token.');
return;
}
$this->line('Full export is running...');
// make repositories
/** @var UserRepositoryInterface $userRepository */
$userRepository = app(UserRepositoryInterface::class);
/** @var ExportJobRepositoryInterface $jobRepository */
$jobRepository = app(ExportJobRepositoryInterface::class);
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
/** @var JournalRepositoryInterface $journalRepository */
$journalRepository = app(JournalRepositoryInterface::class);
// set user
$user = $userRepository->find(intval($this->option('user')));
$jobRepository->setUser($user);
$journalRepository->setUser($user);
$accountRepository->setUser($user);
// first date
$firstJournal = $journalRepository->first();
$first = new Carbon;
if (!is_null($firstJournal->id)) {
$first = $firstJournal->date;
}
// create job and settings.
$job = $jobRepository->create();
$settings = [
'accounts' => $accountRepository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]),
'startDate' => $first,
'endDate' => new Carbon,
'exportFormat' => 'csv',
'includeAttachments' => $this->option('with_attachments'),
'includeOldUploads' => $this->option('with_uploads'),
'job' => $job,
];
/** @var ProcessorInterface $processor */
$processor = app(ProcessorInterface::class);
$processor->setSettings($settings);
$processor->collectJournals();
$processor->convertJournals();
$processor->exportJournals();
if ($settings['includeAttachments']) {
$processor->collectAttachments();
}
if ($settings['includeOldUploads']) {
$processor->collectOldUploads();
}
$processor->createZipFile();
$disk = Storage::disk('export');
$fileName = sprintf('export-%s.zip', date('Y-m-d_H-i-s'));
$disk->move($job->key . '.zip', $fileName);
$this->line('The export has finished! You can find the ZIP file in this location:');
$this->line(storage_path(sprintf('export/%s', $fileName)));
return;
}
}

View File

@@ -30,6 +30,7 @@ use Monolog\Formatter\LineFormatter;
*/
class CreateImport extends Command
{
use VerifiesAccessToken;
/**
* The console command description.
*
@@ -42,7 +43,13 @@ class CreateImport extends Command
*
* @var string
*/
protected $signature = 'firefly:create-import {file} {configuration} {--user=1} {--type=csv} {--start}';
protected $signature = 'firefly:create-import
{file : The file to import.}
{configuration : The configuration file to use for the import/}
{--type=csv : The file type of the import.}
{--user= : The user ID that the import should import for.}
{--token= : The user\'s access token.}
{--start : Starts the job immediately.}';
/**
* Create a new command instance.
@@ -61,6 +68,11 @@ class CreateImport extends Command
*/
public function handle()
{
if (!$this->verifyAccessToken()) {
$this->error('Invalid access token.');
return;
}
/** @var UserRepositoryInterface $userRepository */
$userRepository = app(UserRepositoryInterface::class);
$file = $this->argument('file');
@@ -150,12 +162,12 @@ class CreateImport extends Command
$cwd = getcwd();
$validTypes = array_keys(config('firefly.import_formats'));
$type = strtolower($this->option('type'));
if (is_null($user->id)) {
$this->error(sprintf('There is no user with ID %d.', $this->option('user')));
return false;
}
if (!in_array($type, $validTypes)) {
$this->error(sprintf('Cannot import file of type "%s"', $type));

View File

@@ -51,6 +51,10 @@ class DecryptAttachment extends Command
/**
* Execute the console command.
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five its fine.
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*
*/
public function handle()
{

View File

@@ -35,8 +35,10 @@ use Schema;
/**
* Class UpgradeDatabase
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) // it just touches a lot of things.
* Upgrade user database.
*
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) // it just touches a lot of things.
* @package FireflyIII\Console\Commands
*/
class UpgradeDatabase extends Command
@@ -138,6 +140,9 @@ class UpgradeDatabase extends Command
/**
* Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account.
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's seven but it can't really be helped.
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function updateAccountCurrencies(): void
{
@@ -192,6 +197,8 @@ class UpgradeDatabase extends Command
*
* Both source and destination must match the respective currency preference of the related asset account.
* So FF3 must verify all transactions.
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function updateOtherCurrencies(): void
{
@@ -210,6 +217,9 @@ class UpgradeDatabase extends Command
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->first(['transactions.*']);
if (is_null($transaction)) {
return;
}
/** @var Account $account */
$account = $transaction->account;
$currency = $repository->find(intval($account->getMeta('currency_id')));
@@ -350,6 +360,12 @@ class UpgradeDatabase extends Command
*
* The transaction that is sent to this function MUST be the source transaction (amount negative).
*
* Method is long and complex bit I'm taking it for granted.
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*
* @param Transaction $transaction
*/
private function updateTransactionCurrency(Transaction $transaction): void

View File

@@ -0,0 +1,69 @@
<?php
/**
* VerifiesAccessToken.php
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Console\Commands;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Log;
use Preferences;
/**
* Trait VerifiesAccessToken
*
* Verifies user access token for sensitive commands.
*
* @package FireflyIII\Console\Commands
*/
trait VerifiesAccessToken
{
/**
* Abstract method to make sure trait knows about method "option".
* @param null $key
*
* @return mixed
*/
abstract public function option($key = null);
/**
* Returns false when given token does not match given user token.
*
* @return bool
*/
protected function verifyAccessToken(): bool
{
$userId = intval($this->option('user'));
$token = strval($this->option('token'));
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
$user = $repository->find($userId);
if (is_null($user->id)) {
Log::error(sprintf('verifyAccessToken(): no such user for input "%d"', $userId));
return false;
}
$accessToken = Preferences::getForUser($user, 'access_token', null);
if (is_null($accessToken)) {
Log::error(sprintf('User #%d has no access token, so cannot access command line options.', $userId));
return false;
}
if (!($accessToken->data === $token)) {
Log::error(sprintf('Invalid access token for user #%d.', $userId));
return false;
}
return true;
}
}

View File

@@ -17,6 +17,7 @@ use Crypt;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget;
use FireflyIII\Models\LinkType;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
@@ -26,12 +27,15 @@ use FireflyIII\User;
use Illuminate\Console\Command;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Eloquent\Builder;
use Preferences;
use Schema;
use stdClass;
/**
* Class VerifyDatabase
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*
* @package FireflyIII\Console\Commands
*/
class VerifyDatabase extends Command
@@ -70,38 +74,64 @@ class VerifyDatabase extends Command
$this->reportObject('budget');
$this->reportObject('category');
$this->reportObject('tag');
// accounts with no transactions.
$this->reportAccounts();
// budgets with no limits
$this->reportBudgetLimits();
// budgets with no transactions
// sum of transactions is not zero.
$this->reportSum();
// any deleted transaction journals that have transactions that are NOT deleted:
$this->reportJournals();
// deleted transactions that are connected to a not deleted journal.
$this->reportTransactions();
// deleted accounts that still have not deleted transactions or journals attached to them.
$this->reportDeletedAccounts();
// report on journals with no transactions at all.
$this->reportNoTransactions();
// transfers with budgets.
$this->reportTransfersBudgets();
// report on journals with the wrong types of accounts.
$this->reportIncorrectJournals();
// report (and fix) piggy banks
$this->repairPiggyBanks();
$this->createLinkTypes();
$this->createAccessTokens();
}
/**
* Make sure there are only transfers linked to piggy bank events.
* Create user access tokens, if not present already.
*/
private function createAccessTokens()
{
$users = User::get();
/** @var User $user */
foreach ($users as $user) {
$pref = Preferences::getForUser($user, 'access_token', null);
if (is_null($pref)) {
$token = $user->generateAccessToken();
Preferences::setForUser($user, 'access_token', $token);
$this->line(sprintf('Generated access token for user %s', $user->email));
}
}
}
/**
* Create default link types if necessary.
*/
private function createLinkTypes()
{
$set = [
'Related' => ['relates to', 'relates to'],
'Refund' => ['(partially) refunds', 'is (partially) refunded by'],
'Paid' => ['(partially) pays for', 'is (partially) paid for by'],
'Reimbursement' => ['(partially) reimburses', 'is (partially) reimbursed by'],
];
foreach ($set as $name => $values) {
$link = LinkType::where('name', $name)->where('outward', $values[0])->where('inward', $values[1])->first();
if (is_null($link)) {
$link = new LinkType;
$link->name = $name;
$link->outward = $values[0];
$link->inward = $values[1];
}
$link->editable = false;
$link->save();
}
}
/**
* Eeport (and fix) piggy banks. Make sure there are only transfers linked to piggy bank events.
*/
private function repairPiggyBanks(): void
{

View File

@@ -1,54 +1,26 @@
<?php
declare(strict_types=1);
/**
* Kernel.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Console;
use FireflyIII\Console\Commands\CreateImport;
use FireflyIII\Console\Commands\DecryptAttachment;
use FireflyIII\Console\Commands\EncryptFile;
use FireflyIII\Console\Commands\Import;
use FireflyIII\Console\Commands\ScanAttachments;
use FireflyIII\Console\Commands\UpgradeDatabase;
use FireflyIII\Console\Commands\UpgradeFireflyInstructions;
use FireflyIII\Console\Commands\UseEncryption;
use FireflyIII\Console\Commands\VerifyDatabase;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
/**
* Class Kernel
*
* @package FireflyIII\Console
* File to make sure commnds work.
*/
class Kernel extends ConsoleKernel
{
/**
* The bootstrap classes for the application.
*
* Next upgrade verify these are the same.
*
* @var array
*/
protected $bootstrappers
= [
'Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables',
'Illuminate\Foundation\Bootstrap\LoadConfiguration',
'Illuminate\Foundation\Bootstrap\HandleExceptions',
'Illuminate\Foundation\Bootstrap\RegisterFacades',
'Illuminate\Foundation\Bootstrap\SetRequestForConsole',
'Illuminate\Foundation\Bootstrap\RegisterProviders',
'Illuminate\Foundation\Bootstrap\BootProviders',
];
/**
* The Artisan commands provided by your application.
*
@@ -56,24 +28,29 @@ class Kernel extends ConsoleKernel
*/
protected $commands
= [
UpgradeFireflyInstructions::class,
VerifyDatabase::class,
Import::class,
CreateImport::class,
EncryptFile::class,
ScanAttachments::class,
UpgradeDatabase::class,
UseEncryption::class,
DecryptAttachment::class,
//
];
/**
* Register the Closure based commands for the application.
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
protected function schedule(Schedule $schedule)
{
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* AdminRequestedTestMessage.php
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Events;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
use Log;
/**
* Class AdminRequestedTestMessage
*
* @package FireflyIII\Events
*/
class AdminRequestedTestMessage extends Event
{
use SerializesModels;
public $ipAddress;
public $user;
/**
* Create a new event instance.
*
* @param User $user
* @param string $ipAddress
*/
public function __construct(User $user, string $ipAddress)
{
Log::debug(sprintf('Triggered AdminRequestedTestMessage for user #%d (%s) and IP %s!', $user->id, $user->email, $ipAddress));
$this->user = $user;
$this->ipAddress = $ipAddress;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* UserChangedEmail.php
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Events;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
/**
* Class UserChangedEmail
*
* @package FireflyIII\Events
*/
class UserChangedEmail extends Event
{
use SerializesModels;
/** @var string */
public $ipAddress;
/** @var string */
public $newEmail;
/** @var string */
public $oldEmail;
/** @var User */
public $user;
/**
* UserChangedEmail constructor.
*
* @param User $user
* @param string $newEmail
* @param string $oldEmail
* @param string $ipAddress
*/
public function __construct(User $user, string $newEmail, string $oldEmail, string $ipAddress)
{
$this->user = $user;
$this->ipAddress = $ipAddress;
$this->oldEmail = $oldEmail;
$this->newEmail = $newEmail;
}
}

View File

@@ -1,50 +1,45 @@
<?php
declare(strict_types=1);
/**
* Handler.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Exceptions;
use ErrorException;
use Exception;
use FireflyIII\Jobs\MailError;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
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;
/**
* Class Handler
*
* @package FireflyIII\Exceptions
*/
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that should not be reported.
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash
= [
'password',
'password_confirmation',
];
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport
= [
AuthenticationException::class,
AuthorizationException::class,
HttpException::class,
ModelNotFoundException::class,
TokenMismatchException::class,
ValException::class,
//
];
/**
@@ -67,14 +62,13 @@ class Handler extends ExceptionHandler
return parent::render($request, $exception);
}
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* @param Exception $exception
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five its fine.
* @param \Exception $exception
*
* @return void
*/
@@ -107,22 +101,7 @@ class Handler extends ExceptionHandler
dispatch($job);
}
parent::report($exception);
}
/**
* Convert an authentication exception into an unauthenticated response.
*
* @param $request
*
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
*/
protected function unauthenticated($request)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest('login');
}
}

View File

@@ -122,6 +122,7 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
*/
private function getAttachments(): Collection
{
$this->repository->setUser($this->user);
$attachments = $this->repository->getBetween($this->start, $this->end);
return $attachments;

View File

@@ -15,6 +15,7 @@ namespace FireflyIII\Export\Collector;
use FireflyIII\Models\ExportJob;
use FireflyIII\User;
use Illuminate\Support\Collection;
/**
@@ -26,6 +27,8 @@ class BasicCollector
{
/** @var ExportJob */
protected $job;
/** @var User */
protected $user;
/** @var Collection */
private $entries;
@@ -59,6 +62,15 @@ class BasicCollector
public function setJob(ExportJob $job)
{
$this->job = $job;
$this->user = $job->user;
}
/**
* @param User $user
*/
public function setUser(User $user)
{
$this->user = $user;
}

View File

@@ -1,347 +0,0 @@
<?php
/**
* JournalExportCollector.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);
namespace FireflyIII\Export\Collector;
use Carbon\Carbon;
use DB;
use FireflyIII\Models\Transaction;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
use Steam;
/**
* Class JournalExportCollector
*
* @package FireflyIII\Export\Collector
*/
class JournalExportCollector extends BasicCollector implements CollectorInterface
{
/** @var Collection */
private $accounts;
/** @var Carbon */
private $end;
/** @var Carbon */
private $start;
/** @var Collection */
private $workSet;
/**
* @return bool
*/
public function run(): bool
{
/*
* Instead of collecting journals we collect transactions for the given accounts.
* We left join the OPPOSING transaction AND some journal data.
* After that we complement this info with budgets, categories, etc.
*
* This is way more efficient and will also work on split journals.
*/
$this->getWorkSet();
/*
* Extract:
* possible budget ids for journals
* possible category ids journals
* possible budget ids for transactions
* possible category ids for transactions
*
* possible IBAN and account numbers?
*
*/
$journals = $this->extractJournalIds();
$transactions = $this->extractTransactionIds();
// extend work set with category data from journals:
$this->categoryDataForJournals($journals);
// extend work set with category cate from transactions (overrules journals):
$this->categoryDataForTransactions($transactions);
// same for budgets:
$this->budgetDataForJournals($journals);
$this->budgetDataForTransactions($transactions);
$this->setEntries($this->workSet);
return true;
}
/**
* @param Collection $accounts
*/
public function setAccounts(Collection $accounts)
{
$this->accounts = $accounts;
}
/**
* @param Carbon $start
* @param Carbon $end
*/
public function setDates(Carbon $start, Carbon $end)
{
$this->start = $start;
$this->end = $end;
}
/**
* @param array $journals
*
* @return bool
*/
private function budgetDataForJournals(array $journals): bool
{
$set = DB::table('budget_transaction_journal')
->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction_journal.budget_id')
->whereIn('budget_transaction_journal.transaction_journal_id', $journals)
->get(
[
'budget_transaction_journal.budget_id',
'budget_transaction_journal.transaction_journal_id',
'budgets.name',
'budgets.encrypted',
]
);
$set->each(
function ($obj) {
$obj->name = Steam::decrypt(intval($obj->encrypted), $obj->name);
}
);
$array = [];
foreach ($set as $obj) {
$array[$obj->transaction_journal_id] = ['id' => $obj->budget_id, 'name' => $obj->name];
}
$this->workSet->each(
function ($obj) use ($array) {
if (isset($array[$obj->transaction_journal_id])) {
$obj->budget_id = $array[$obj->transaction_journal_id]['id'];
$obj->budget_name = $array[$obj->transaction_journal_id]['name'];
}
}
);
return true;
}
/**
* @param array $transactions
*
* @return bool
*/
private function budgetDataForTransactions(array $transactions): bool
{
$set = DB::table('budget_transaction')
->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction.budget_id')
->whereIn('budget_transaction.transaction_id', $transactions)
->get(
[
'budget_transaction.budget_id',
'budget_transaction.transaction_id',
'budgets.name',
'budgets.encrypted',
]
);
$set->each(
function ($obj) {
$obj->name = Steam::decrypt(intval($obj->encrypted), $obj->name);
}
);
$array = [];
foreach ($set as $obj) {
$array[$obj->transaction_id] = ['id' => $obj->budget_id, 'name' => $obj->name];
}
$this->workSet->each(
function ($obj) use ($array) {
// first transaction
if (isset($array[$obj->id])) {
$obj->budget_id = $array[$obj->id]['id'];
$obj->budget_name = $array[$obj->id]['name'];
}
}
);
return true;
}
/**
* @param array $journals
*
* @return bool
*/
private function categoryDataForJournals(array $journals): bool
{
$set = DB::table('category_transaction_journal')
->leftJoin('categories', 'categories.id', '=', 'category_transaction_journal.category_id')
->whereIn('category_transaction_journal.transaction_journal_id', $journals)
->get(
[
'category_transaction_journal.category_id',
'category_transaction_journal.transaction_journal_id',
'categories.name',
'categories.encrypted',
]
);
$set->each(
function ($obj) {
$obj->name = Steam::decrypt(intval($obj->encrypted), $obj->name);
}
);
$array = [];
foreach ($set as $obj) {
$array[$obj->transaction_journal_id] = ['id' => $obj->category_id, 'name' => $obj->name];
}
$this->workSet->each(
function ($obj) use ($array) {
if (isset($array[$obj->transaction_journal_id])) {
$obj->category_id = $array[$obj->transaction_journal_id]['id'];
$obj->category_name = $array[$obj->transaction_journal_id]['name'];
}
}
);
return true;
}
/**
* @param array $transactions
*
* @return bool
*/
private function categoryDataForTransactions(array $transactions): bool
{
$set = DB::table('category_transaction')
->leftJoin('categories', 'categories.id', '=', 'category_transaction.category_id')
->whereIn('category_transaction.transaction_id', $transactions)
->get(
[
'category_transaction.category_id',
'category_transaction.transaction_id',
'categories.name',
'categories.encrypted',
]
);
$set->each(
function ($obj) {
$obj->name = Steam::decrypt(intval($obj->encrypted), $obj->name);
}
);
$array = [];
foreach ($set as $obj) {
$array[$obj->transaction_id] = ['id' => $obj->category_id, 'name' => $obj->name];
}
$this->workSet->each(
function ($obj) use ($array) {
// first transaction
if (isset($array[$obj->id])) {
$obj->category_id = $array[$obj->id]['id'];
$obj->category_name = $array[$obj->id]['name'];
}
}
);
return true;
}
/**
* @return array
*/
private function extractJournalIds(): array
{
return $this->workSet->pluck('transaction_journal_id')->toArray();
}
/**
* @return array
*/
private function extractTransactionIds()
{
$set = $this->workSet->pluck('id')->toArray();
$opposing = $this->workSet->pluck('opposing_id')->toArray();
$complete = $set + $opposing;
return array_unique($complete);
}
/**
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
private function getWorkSet()
{
$accountIds = $this->accounts->pluck('id')->toArray();
$this->workSet = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin(
'transactions AS opposing', function (JoinClause $join) {
$join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id')
->where('opposing.amount', '=', DB::raw('transactions.amount * -1'))
->where('transactions.identifier', '=', DB::raw('opposing.identifier'));
}
)
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id')
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', 'transaction_types.id')
->leftJoin('transaction_currencies', 'transactions.transaction_currency_id', '=', 'transaction_currencies.id')
->whereIn('transactions.account_id', $accountIds)
->where('transaction_journals.user_id', $this->job->user_id)
->where('transaction_journals.date', '>=', $this->start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $this->end->format('Y-m-d'))
->where('transaction_journals.completed', 1)
->whereNull('transaction_journals.deleted_at')
->whereNull('transactions.deleted_at')
->whereNull('opposing.deleted_at')
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transactions.identifier', 'ASC')
->get(
[
'transactions.id',
'transactions.amount',
'transactions.description',
'transactions.account_id',
'accounts.name as account_name',
'accounts.encrypted as account_name_encrypted',
'transactions.identifier',
'opposing.id as opposing_id',
'opposing.amount AS opposing_amount',
'opposing.description as opposing_description',
'opposing.account_id as opposing_account_id',
'opposing_accounts.name as opposing_account_name',
'opposing_accounts.encrypted as opposing_account_encrypted',
'opposing.identifier as opposing_identifier',
'transaction_journals.id as transaction_journal_id',
'transaction_journals.date',
'transaction_journals.description as journal_description',
'transaction_journals.encrypted as journal_encrypted',
'transaction_journals.transaction_type_id',
'transaction_types.type as transaction_type',
'transactions.transaction_currency_id',
'transaction_currencies.code AS transaction_currency_code',
]
);
}
}

View File

@@ -30,8 +30,6 @@ class UploadCollector extends BasicCollector implements CollectorInterface
private $exportDisk;
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
private $uploadDisk;
/** @var string */
private $vintageFormat;
/**
* AttachmentCollector constructor.

View File

@@ -31,6 +31,7 @@ use Steam;
*
* Class Entry
* @SuppressWarnings(PHPMD.LongVariable)
* @SuppressWarnings(PHPMD.TooManyFields)
*
* @package FireflyIII\Export\Entry
*/
@@ -84,40 +85,12 @@ final class Entry
{
}
/**
* @param $object
*
* @return Entry
*/
public static function fromObject($object): Entry
{
$entry = new self;
$entry->journal_id = $object->transaction_journal_id;
$entry->description = Steam::decrypt(intval($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->asset_account_id = $object->account_id;
$entry->asset_account_name = Steam::decrypt(intval($object->account_name_encrypted), $object->account_name);
$entry->opposing_account_id = $object->opposing_account_id;
$entry->opposing_account_name = Steam::decrypt(intval($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) {
$entry->description = $entry->description . ' (' . $object->description . ')';
}
return $entry;
}
/**
* Converts a given transaction (as collected by the collector) into an export entry.
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // complex but little choice.
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // cannot be helped
*
* @param Transaction $transaction
*
* @return Entry

View File

@@ -34,6 +34,8 @@ use ZipArchive;
/**
* Class ExpandedProcessor
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) // its doing a lot.
*
* @package FireflyIII\Export
*/
class ExpandedProcessor implements ProcessorInterface
@@ -84,13 +86,17 @@ class ExpandedProcessor implements ProcessorInterface
}
/**
* Collects all transaction journals.
*
* @return bool
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function collectJournals(): bool
{
// use journal collector thing.
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setUser($this->job->user);
$collector->setAccounts($this->accounts)->setRange($this->settings['startDate'], $this->settings['endDate'])
->withOpposingAccount()->withBudgetInformation()->withCategoryInformation()
->removeFilter(InternalTransferFilter::class);
@@ -101,6 +107,7 @@ class ExpandedProcessor implements ProcessorInterface
$opposingIds = $transactions->pluck('opposing_account_id')->toArray();
$notes = $this->getNotes($ids);
$tags = $this->getTags($ids);
/** @var array $ibans */
$ibans = $this->getIbans($assetIds) + $this->getIbans($opposingIds);
$currencies = $this->getAccountCurrencies($ibans);
$transactions->each(

View File

@@ -1,203 +0,0 @@
<?php
/**
* Processor.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);
namespace FireflyIII\Export;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Export\Collector\AttachmentCollector;
use FireflyIII\Export\Collector\JournalExportCollector;
use FireflyIII\Export\Collector\UploadCollector;
use FireflyIII\Export\Entry\Entry;
use FireflyIII\Models\ExportJob;
use Illuminate\Support\Collection;
use Log;
use Storage;
use ZipArchive;
/**
* Class Processor
*
* @package FireflyIII\Export
*/
class Processor implements ProcessorInterface
{
/** @var Collection */
public $accounts;
/** @var string */
public $exportFormat;
/** @var bool */
public $includeAttachments;
/** @var bool */
public $includeOldUploads;
/** @var ExportJob */
public $job;
/** @var array */
public $settings;
/** @var Collection */
private $exportEntries;
/** @var Collection */
private $files;
/** @var Collection */
private $journals;
/**
* Processor constructor.
*/
public function __construct()
{
$this->journals = new Collection;
$this->exportEntries = new Collection;
$this->files = new Collection;
}
/**
* @return bool
*/
public function collectAttachments(): bool
{
/** @var AttachmentCollector $attachmentCollector */
$attachmentCollector = app(AttachmentCollector::class);
$attachmentCollector->setJob($this->job);
$attachmentCollector->setDates($this->settings['startDate'], $this->settings['endDate']);
$attachmentCollector->run();
$this->files = $this->files->merge($attachmentCollector->getEntries());
return true;
}
/**
* @return bool
*/
public function collectJournals(): bool
{
/** @var JournalExportCollector $collector */
$collector = app(JournalExportCollector::class);
$collector->setJob($this->job);
$collector->setDates($this->settings['startDate'], $this->settings['endDate']);
$collector->setAccounts($this->settings['accounts']);
$collector->run();
$this->journals = $collector->getEntries();
Log::debug(sprintf('Count %d journals in collectJournals() ', $this->journals->count()));
return true;
}
/**
* @return bool
*/
public function collectOldUploads(): bool
{
/** @var UploadCollector $uploadCollector */
$uploadCollector = app(UploadCollector::class);
$uploadCollector->setJob($this->job);
$uploadCollector->run();
$this->files = $this->files->merge($uploadCollector->getEntries());
return true;
}
/**
* @return bool
*/
public function convertJournals(): bool
{
$count = 0;
foreach ($this->journals as $object) {
$this->exportEntries->push(Entry::fromObject($object));
$count++;
}
Log::debug(sprintf('Count %d entries in exportEntries (convertJournals)', $this->exportEntries->count()));
return true;
}
/**
* @return bool
* @throws FireflyException
*/
public function createZipFile(): bool
{
$zip = new ZipArchive;
$file = $this->job->key . '.zip';
$fullPath = storage_path('export') . '/' . $file;
if ($zip->open($fullPath, ZipArchive::CREATE) !== true) {
throw new FireflyException('Cannot store zip file.');
}
// for each file in the collection, add it to the zip file.
$disk = Storage::disk('export');
foreach ($this->getFiles() as $entry) {
// is part of this job?
$zipFileName = str_replace($this->job->key . '-', '', $entry);
$zip->addFromString($zipFileName, $disk->get($entry));
}
$zip->close();
// delete the files:
$this->deleteFiles();
return true;
}
/**
* @return bool
*/
public function exportJournals(): bool
{
$exporterClass = config('firefly.export_formats.' . $this->exportFormat);
$exporter = app($exporterClass);
$exporter->setJob($this->job);
$exporter->setEntries($this->exportEntries);
$exporter->run();
$this->files->push($exporter->getFileName());
return true;
}
/**
* @return Collection
*/
public function getFiles(): Collection
{
return $this->files;
}
/**
* @param array $settings
*/
public function setSettings(array $settings)
{
// save settings
$this->settings = $settings;
$this->accounts = $settings['accounts'];
$this->exportFormat = $settings['exportFormat'];
$this->includeAttachments = $settings['includeAttachments'];
$this->includeOldUploads = $settings['includeOldUploads'];
$this->job = $settings['job'];
}
/**
*
*/
private function deleteFiles()
{
$disk = Storage::disk('export');
foreach ($this->getFiles() as $file) {
$disk->delete($file);
}
}
}

View File

@@ -145,6 +145,9 @@ class MonthReportGenerator implements ReportGeneratorInterface
* @param Carbon $date
*
* @return array
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // not that long
*
*/
private function getAuditReport(Account $account, Carbon $date): array
{
@@ -175,9 +178,6 @@ class MonthReportGenerator implements ReportGeneratorInterface
$transaction->currency = $currency;
}
/*
* Reverse set again.
*/
$return = [
'journals' => $journals->reverse(),
'exists' => $journals->count() > 0,

View File

@@ -0,0 +1,55 @@
<?php
/**
* AdminEventHandler.php
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\AdminRequestedTestMessage;
use FireflyIII\Mail\AdminTestMail;
use Log;
use Mail;
use Session;
use Swift_TransportException;
/**
* Class AdminEventHandler
*
* @package FireflyIII\Handlers\Events
*/
class AdminEventHandler
{
/**
* @param AdminRequestedTestMessage $event
*
* @return bool
*/
public function sendTestMessage(AdminRequestedTestMessage $event): bool
{
$email = $event->user->email;
$ipAddress = $event->ipAddress;
Log::debug(sprintf('Now in sendTestMessage event handler. Email is %s, IP is %s', $email, $ipAddress));
try {
Log::debug('Trying to send message...');
Mail::to($email)->send(new AdminTestMail($email, $ipAddress));
// @codeCoverageIgnoreStart
} catch (Swift_TransportException $e) {
Log::debug('Send message failed! :(');
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
Session::flash('error', 'Possible email error: ' . $e->getMessage());
}
Log::debug('If no error above this line, message was sent.');
// @codeCoverageIgnoreEnd
return true;
}
}

View File

@@ -19,8 +19,8 @@ use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface as JRI;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface as PRI;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface as RGRI;
use FireflyIII\Rules\Processor;
use FireflyIII\Support\Events\BillScanner;
use FireflyIII\TransactionRules\Processor;
use Log;
/**
@@ -57,11 +57,12 @@ class StoredJournalEventHandler
/**
* This method connects a new transfer to a piggy bank.
*
*
*
* @param StoredTransactionJournal $event
*
* @return bool
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function connectToPiggyBank(StoredTransactionJournal $event): bool
{

View File

@@ -18,8 +18,8 @@ use FireflyIII\Events\UpdatedTransactionJournal;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Rules\Processor;
use FireflyIII\Support\Events\BillScanner;
use FireflyIII\TransactionRules\Processor;
/**
* @codeCoverageIgnore

View File

@@ -15,11 +15,15 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\UserChangedEmail;
use FireflyIII\Mail\ConfirmEmailChangeMail;
use FireflyIII\Mail\RegisteredUser as RegisteredUserMail;
use FireflyIII\Mail\RequestedNewPassword as RequestedNewPasswordMail;
use FireflyIII\Mail\UndoEmailChangeMail;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Log;
use Mail;
use Preferences;
use Swift_TransportException;
/**
@@ -54,6 +58,54 @@ class UserEventHandler
return true;
}
/**
* @param UserChangedEmail $event
*
* @return bool
*/
public function sendEmailChangeConfirmMail(UserChangedEmail $event): bool
{
$newEmail = $event->newEmail;
$oldEmail = $event->oldEmail;
$user = $event->user;
$ipAddress = $event->ipAddress;
$token = Preferences::getForUser($user, 'email_change_confirm_token', 'invalid');
$uri = route('profile.confirm-email-change', [$token->data]);
try {
Mail::to($newEmail)->send(new ConfirmEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress));
// @codeCoverageIgnoreStart
} catch (Swift_TransportException $e) {
Log::error($e->getMessage());
}
// @codeCoverageIgnoreEnd
return true;
}
/**
* @param UserChangedEmail $event
*
* @return bool
*/
public function sendEmailChangeUndoMail(UserChangedEmail $event): bool
{
$newEmail = $event->newEmail;
$oldEmail = $event->oldEmail;
$user = $event->user;
$ipAddress = $event->ipAddress;
$token = Preferences::getForUser($user, 'email_change_undo_token', 'invalid');
$uri = route('profile.undo-email-change', [$token->data, hash('sha256', $oldEmail)]);
try {
Mail::to($oldEmail)->send(new UndoEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress));
// @codeCoverageIgnoreStart
} catch (Swift_TransportException $e) {
Log::error($e->getMessage());
}
// @codeCoverageIgnoreEnd
return true;
}
/**
* @param RequestedNewPassword $event
*

View File

@@ -169,7 +169,6 @@ class AttachmentHelper implements AttachmentHelperInterface
// store it:
$this->uploadDisk->put($attachment->fileName(), $encrypted);
$attachment->uploaded = 1; // update attachment
$attachment->save();
$this->attachments->push($attachment);
@@ -180,8 +179,6 @@ class AttachmentHelper implements AttachmentHelperInterface
// return it.
return $attachment;
}
/**

View File

@@ -32,6 +32,8 @@ use Steam;
* Class MetaPieChart
*
* @package FireflyIII\Helpers\Chart
*
*
*/
class MetaPieChart implements MetaPieChartInterface
{
@@ -83,12 +85,15 @@ class MetaPieChart implements MetaPieChartInterface
* @param string $group
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function generate(string $direction, string $group): array
{
$transactions = $this->getTransactions($direction);
$grouped = $this->groupByFields($transactions, $this->grouping[$group]);
$chartData = $this->organizeByType($group, $grouped);
$key = strval(trans('firefly.everything_else'));
// also collect all other transactions
if ($this->collectOtherObjects && $direction === 'expense') {
@@ -96,11 +101,12 @@ class MetaPieChart implements MetaPieChartInterface
$collector = app(JournalCollectorInterface::class);
$collector->setUser($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;
$chartData[$key] = $sum;
}
if ($this->collectOtherObjects && $direction === 'income') {
@@ -111,7 +117,7 @@ class MetaPieChart implements MetaPieChartInterface
$journals = $collector->getJournals();
$sum = strval($journals->sum('transaction_amount'));
$sum = bcsub($sum, $this->total);
$chartData[strval(trans('firefly.everything_else'))] = $sum;
$chartData[$key] = $sum;
}
return $chartData;
@@ -258,12 +264,9 @@ class MetaPieChart implements MetaPieChartInterface
$collector->removeFilter(TransferFilter::class);
}
if ($this->budgets->count() > 0) {
$collector->setBudgets($this->budgets);
}
if ($this->categories->count() > 0) {
$collector->setCategories($this->categories);
}
if ($this->tags->count() > 0) {
$collector->setTags($this->tags);
$collector->withCategoryInformation();
@@ -278,6 +281,9 @@ class MetaPieChart implements MetaPieChartInterface
* @param array $fields
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*
*/
protected function groupByFields(Collection $set, array $fields): array
{

View File

@@ -43,6 +43,9 @@ use Steam;
* Class JournalCollector
*
* @package FireflyIII\Helpers\Collector
*
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class JournalCollector implements JournalCollectorInterface
{
@@ -413,10 +416,10 @@ class JournalCollector implements JournalCollectorInterface
$this->offset = $offset;
$this->query->skip($offset);
Log::debug(sprintf('Changed offset to %d', $offset));
return $this;
}
if (is_null($this->limit)) {
Log::debug('The limit is zero, cannot set the page.');
}
return $this;
}

View File

@@ -67,9 +67,11 @@ class BalanceReportHelper implements BalanceReportHelperInterface
/** @var BudgetLimit $budgetLimit */
foreach ($budgetLimits as $budgetLimit) {
if (!is_null($budgetLimit->budget)) {
$line = $this->createBalanceLine($budgetLimit, $accounts);
$balance->addBalanceLine($line);
}
}
$noBudgetLine = $this->createNoBudgetLine($accounts, $start, $end);
$balance->addBalanceLine($noBudgetLine);

View File

@@ -42,6 +42,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface
/**
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5.
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // all the arrays make it long.
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts

View File

@@ -39,6 +39,7 @@ use View;
* Class AccountController
*
* @package FireflyIII\Http\Controllers
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class AccountController extends Controller
{
@@ -141,9 +142,14 @@ class AccountController extends Controller
}
/**
* Edit an account.
*
* @param Request $request
* @param Account $account
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // long and complex but not that excessively so.
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*
* @return View
*/
public function edit(Request $request, Account $account)
@@ -237,12 +243,16 @@ class AccountController extends Controller
/**
* Show an account.
* @param Request $request
* @param JournalRepositoryInterface $repository
* @param Account $account
* @param string $moment
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // long and complex but not that excessively so.
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function show(Request $request, JournalRepositoryInterface $repository, Account $account, string $moment = '')
{
@@ -389,6 +399,8 @@ class AccountController extends Controller
* @param Account $account The account involved.
*
* @return Collection
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
private function getPeriodOverview(Account $account): Collection
{
@@ -399,7 +411,7 @@ class AccountController extends Controller
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfX(new Carbon, $range, null);
$entries = new Collection;
$count = 0;
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
@@ -412,24 +424,20 @@ class AccountController extends Controller
}
Log::debug('Going to get period expenses and incomes.');
while ($end >= $start) {
while ($end >= $start && $count < 90) {
$end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($end, $range);
// try a collector for income:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)
->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)->setTypes([TransactionType::DEPOSIT])->withOpposingAccount();
$earned = strval($collector->getJournals()->sum('transaction_amount'));
// try a collector for expenses:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)
->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)->setTypes([TransactionType::WITHDRAWAL])->withOpposingAccount();
$spent = strval($collector->getJournals()->sum('transaction_amount'));
$dateStr = $end->format('Y-m-d');
$dateName = Navigation::periodShow($end, $range);
@@ -442,7 +450,7 @@ class AccountController extends Controller
'date' => clone $end]
);
$end = Navigation::subtractPeriod($end, $range, 1);
$count++;
}
$cache->store($entries);

View File

@@ -14,7 +14,11 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Events\AdminRequestedTestMessage;
use FireflyIII\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Session;
use Log;
/**
* Class HomeController
@@ -34,4 +38,18 @@ class HomeController extends Controller
return view('admin.index', compact('title', 'mainTitleIcon'));
}
/**
* @param Request $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function testMessage(Request $request)
{
$ipAddress = $request->ip();
Log::debug(sprintf('Now in testMessage() controller. IP is %s', $ipAddress));
event(new AdminRequestedTestMessage(auth()->user(), $ipAddress));
Session::flash('info', strval(trans('firefly.send_test_triggered')));
return redirect(route('admin.index'));
}
}

View File

@@ -48,6 +48,32 @@ class UserController extends Controller
);
}
/**
* @param User $user
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function delete(User $user)
{
$subTitle = trans('firefly.delete_user', ['email' => $user->email]);
return view('admin.users.delete', compact('user', 'subTitle'));
}
/**
* @param User $user
* @param UserRepositoryInterface $repository
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(User $user, UserRepositoryInterface $repository)
{
$repository->destroy($user);
Session::flash('success', strval(trans('firefly.user_deleted')));
return redirect(route('admin.users'));
}
/**
* @param User $user
*
@@ -67,6 +93,7 @@ class UserController extends Controller
'' => strval(trans('firefly.no_block_code')),
'bounced' => strval(trans('firefly.block_code_bounced')),
'expired' => strval(trans('firefly.block_code_expired')),
'email_changed' => strval(trans('firefly.block_code_email_changed')),
];
return view('admin.users.edit', compact('user', 'subTitle', 'subTitleIcon', 'codes'));
@@ -143,6 +170,7 @@ class UserController extends Controller
}
$repository->changeStatus($user, $data['blocked'], $data['blocked_code']);
$repository->updateEmail($user, $data['email']);
Session::flash('success', strval(trans('firefly.updated_user', ['email' => $user->email])));
Preferences::mark();

View File

@@ -99,7 +99,6 @@ class AttachmentController extends Controller
public function download(AttachmentRepositoryInterface $repository, Attachment $attachment)
{
if ($repository->exists($attachment)) {
$content = $repository->getContent($attachment);
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));

View File

@@ -1,31 +1,34 @@
<?php
declare(strict_types=1);
/**
* ForgotPasswordController.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Auth;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Password;
/**
* Class ForgotPasswordController
*
* @package FireflyIII\Http\Controllers\Auth
*/
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
/**
@@ -36,37 +39,6 @@ class ForgotPasswordController extends Controller
{
parent::__construct();
$this->middleware('guest');
}
/**
* Send a reset link to the given user.
*
* @param Request $request
*
* @param UserRepositoryInterface $repository
*
* @return \Illuminate\Http\RedirectResponse
*/
public function sendResetLinkEmail(Request $request, UserRepositoryInterface $repository)
{
$this->validate($request, ['email' => 'required|email']);
// verify if the user is not a demo user. If so, we give him back an error.
$user = User::where('email', $request->get('email'))->first();
if (!is_null($user) && $repository->hasRole($user, 'demo')) {
return back()->withErrors(['email' => trans('firefly.cannot_reset_demo_user')]);
}
$response = $this->broker()->sendResetLink($request->only('email'));
if ($response === Password::RESET_LINK_SENT) {
return back()->with('status', trans($response));
}
// If an error was returned by the password broker, we will get this message
// translated so we can notify a user of the problem. We'll redirect back
// to where the users came from so they can attempt this process again.
return back()->withErrors(['email' => trans($response)]); // @codeCoverageIgnore
}
}

View File

@@ -1,38 +1,49 @@
<?php
declare(strict_types=1);
/**
* LoginController.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Auth;
use Config;
use FireflyConfig;
use FireflyIII\Events\UserChangedEmail;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\User;
use Illuminate\Cookie\CookieJar;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Lang;
/**
* @codeCoverageIgnore
*
* Class LoginController
*
* @package FireflyIII\Http\Controllers\Auth
*/
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
@@ -40,79 +51,11 @@ class LoginController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware('guest', ['except' => 'logout']);
$this->middleware('guest')->except('logout');
}
/**
* Handle a login request to the application.
*
* @param Request $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
*/
public function login(Request $request)
{
$this->validateLogin($request);
$lockedOut = $this->hasTooManyLoginAttempts($request);
if ($lockedOut) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
$credentials = $this->credentials($request);
$credentials['blocked'] = 0; // must not be blocked.
if ($this->guard()->attempt($credentials, $request->has('remember'))) {
return $this->sendLoginResponse($request);
}
$errorMessage = $this->getBlockedError($credentials['email']);
if (!$lockedOut) {
$this->incrementLoginAttempts($request);
}
return $this->sendFailedLoginResponse($request, $errorMessage);
}
/**
* @param Request $request
* @param CookieJar $cookieJar
*
* @return $this
*/
public function logout(Request $request, CookieJar $cookieJar)
{
if (intval(getenv('SANDSTORM')) === 1) {
return view('error')->with('message', strval(trans('firefly.sandstorm_not_available')));
}
$cookie = $cookieJar->forever('twoFactorAuthenticated', 'false');
$this->guard()->logout();
$request->session()->flush();
$request->session()->regenerate();
return redirect('/')->withCookie($cookie);
}
/**
* @return string
*/
public function redirectTo(): string
{
return route('index');
}
/**
* Show the application login form.
*
* @param Request $request
*
* @param CookieJar $cookieJar
* Show the application's login form.
*
* @return \Illuminate\Http\Response
*/
@@ -120,8 +63,9 @@ class LoginController extends Controller
{
// forget 2fa cookie:
$cookie = $cookieJar->forever('twoFactorAuthenticated', 'false');
// is allowed to?
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$userCount = User::count();
$allowRegistration = true;
if ($singleUserMode === true && $userCount > 0) {
@@ -133,59 +77,4 @@ class LoginController extends Controller
return view('auth.login', compact('allowRegistration', 'email', 'remember'))->withCookie($cookie);
}
/**
* Get the failed login message.
*
* @param string $message
*
* @return string
*/
protected function getFailedLoginMessage(string $message)
{
if (strlen($message) > 0) {
return $message;
}
return Lang::has('auth.failed') ? Lang::get('auth.failed') : 'These credentials do not match our records.';
}
/**
* Get the failed login response instance.
*
* @param \Illuminate\Http\Request $request
* @param string $message
*
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendFailedLoginResponse(Request $request, string $message)
{
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors(
[
$this->username() => $this->getFailedLoginMessage($message),
]
);
}
/**
* @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;
}
}

View File

@@ -1,88 +0,0 @@
<?php
/**
* PasswordController.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);
namespace FireflyIII\Http\Controllers\Auth;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\User;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
use Illuminate\Mail\Message;
use Illuminate\Support\Facades\Password;
/**
* @codeCoverageIgnore
*
* Class PasswordController
*
* @package FireflyIII\Http\Controllers\Auth
* @method getEmailSubject()
* @method getSendResetLinkEmailSuccessResponse(string $response)
* @method getSendResetLinkEmailFailureResponse(string $response)
*/
class PasswordController extends Controller
{
use ResetsPasswords;
/**
* Create a new password controller instance.
*
*/
public function __construct()
{
parent::__construct();
$this->middleware('guest');
}
/**
* Send a reset link to the given user.
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 7 but ok
*
* @param \Illuminate\Http\Request $request
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function sendResetLinkEmail(Request $request)
{
$this->validate($request, ['email' => 'required|email']);
$user = User::whereEmail($request->get('email'))->first();
$response = 'passwords.blocked';
if (is_null($user)) {
$response = Password::INVALID_USER;
}
if (!is_null($user) && intval($user->blocked) === 0) {
$response = Password::sendResetLink(
$request->only('email'), function (Message $message) {
$message->subject($this->getEmailSubject());
}
);
}
switch ($response) {
case Password::RESET_LINK_SENT:
return $this->getSendResetLinkEmailSuccessResponse($response);
case Password::INVALID_USER:
case 'passwords.blocked':
default:
return $this->getSendResetLinkEmailFailureResponse($response);
}
}
}

View File

@@ -1,43 +1,44 @@
<?php
declare(strict_types=1);
/**
* RegisterController.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Auth;
use Auth;
use Config;
use FireflyConfig;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\UserRegistrationRequest;
use FireflyIII\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Session;
use Validator;
/**
* @codeCoverageIgnore
*
* Class RegisterController
*
* @package FireflyIII\Http\Controllers\Auth
*/
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after login / registration.
* Where to redirect users after registration.
*
* @var string
*/
@@ -45,6 +46,7 @@ class RegisterController extends Controller
/**
* Create a new controller instance.
*
*/
public function __construct()
{
@@ -53,14 +55,16 @@ class RegisterController extends Controller
}
/**
* @param UserRegistrationRequest|Request $request
* Handle a registration request for the application.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\Response
*/
public function register(UserRegistrationRequest $request)
public function register(Request $request)
{
// is allowed to?
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$userCount = User::count();
if ($singleUserMode === true && $userCount > 0) {
$message = 'Registration is currently not available.';
@@ -68,29 +72,19 @@ class RegisterController extends Controller
return view('error', compact('message'));
}
$this->validator($request->all())->validate();
$validator = $this->validator($request->all());
event(new Registered($user = $this->create($request->all())));
if ($validator->fails()) {
$this->throwValidationException($request, $validator);
}
$user = $this->create($request->all());
// trigger user registration event:
event(new RegisteredUser($user, $request->ip()));
Auth::login($user);
$this->guard()->login($user);
Session::flash('success', strval(trans('firefly.registered')));
Session::flash('gaEventCategory', 'user');
Session::flash('gaEventAction', 'new-registration');
return redirect($this->redirectPath());
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
/**
* OLD
* Show the application registration form.
*
* @param Request $request
@@ -100,10 +94,10 @@ class RegisterController extends Controller
public function showRegistrationForm(Request $request)
{
// is demo site?
$isDemoSite = FireflyConfig::get('is_demo_site', Config::get('firefly.configuration.is_demo_site'))->data;
$isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
// is allowed to?
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$userCount = User::count();
if ($singleUserMode === true && $userCount > 0) {
$message = 'Registration is currently not available.';
@@ -113,6 +107,7 @@ class RegisterController extends Controller
$email = $request->old('email');
return view('auth.register', compact('isDemoSite', 'email'));
}
@@ -121,19 +116,16 @@ class RegisterController extends Controller
*
* @param array $data
*
* @return User
* @return \FireflyIII\User
*/
protected function create(array $data)
{
/** @var User $user */
$user = User::create(
return User::create(
[
'email' => $data['email'],
'password' => bcrypt($data['password']),
]
);
return $user;
}
/**
@@ -147,8 +139,8 @@ class RegisterController extends Controller
{
return Validator::make(
$data, [
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6|confirmed',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|secure_password|confirmed',
]
);
}

View File

@@ -1,32 +1,43 @@
<?php
declare(strict_types=1);
/**
* ResetPasswordController.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Auth;
use FireflyIII\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
/**
* @codeCoverageIgnore
*
* Class ResetPasswordController
*
* @package FireflyIII\Http\Controllers\Auth
*/
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Where to redirect users after resetting their password.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
@@ -34,7 +45,6 @@ class ResetPasswordController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware('guest');
}
}

View File

@@ -34,6 +34,8 @@ class TwoFactorController extends Controller
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function index(Request $request)
{

View File

@@ -40,6 +40,7 @@ use View;
* Class BudgetController
*
* @package FireflyIII\Http\Controllers
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class BudgetController extends Controller
{
@@ -168,6 +169,9 @@ class BudgetController extends Controller
* @param string|null $moment
*
* @return View
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity) complex because of while loop
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function index(string $moment = null)
{
@@ -182,7 +186,6 @@ class BudgetController extends Controller
$end = Navigation::endOfPeriod($start, $range);
} catch (Exception $e) {
// start and end are already defined.
}
}
$next = clone $end;
@@ -190,16 +193,12 @@ class BudgetController extends Controller
$prev = clone $start;
$prev->subDay();
$prev = Navigation::startOfPeriod($prev, $range);
$this->repository->cleanupBudgets();
$budgets = $this->repository->getActiveBudgets();
$inactive = $this->repository->getInactiveBudgets();
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
$budgetInformation = $this->collectBudgetInformation($budgets, $start, $end);
$budgetInformation = $this->repository->collectBudgetInformation($budgets, $start, $end);
$defaultCurrency = Amount::getDefaultCurrency();
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
$spent = array_sum(array_column($budgetInformation, 'spent'));
@@ -246,12 +245,80 @@ class BudgetController extends Controller
);
}
/**
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function infoIncome(Carbon $start, Carbon $end)
{
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('info-income');
if ($cache->has()) {
$result = $cache->get(); // @codeCoverageIgnore
}
if (!$cache->has()) {
$result = [
'available' => '0',
'earned' => '0',
'suggested' => '0',
];
$currency = Amount::getDefaultCurrency();
$range = Preferences::get('viewRange', '1M')->data;
$begin = Navigation::subtractPeriod($start, $range, 3);
// get average amount available.
$total = '0';
$count = 0;
$currentStart = clone $begin;
while ($currentStart < $start) {
$currentEnd = Navigation::endOfPeriod($currentStart, $range);
$total = bcadd($total, $this->repository->getAvailableBudget($currency, $currentStart, $currentEnd));
$currentStart = Navigation::addPeriod($currentStart, $range, 0);
$count++;
}
$result['available'] = bcdiv($total, strval($count));
// amount earned in this period:
$subDay = clone $end;
$subDay->subDay();
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($begin, $subDay)->setTypes([TransactionType::DEPOSIT])->withOpposingAccount();
$result['earned'] = bcdiv(strval($collector->getJournals()->sum('transaction_amount')), strval($count));
// amount spent in period
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($begin, $subDay)->setTypes([TransactionType::WITHDRAWAL])->withOpposingAccount();
$result['spent'] = bcdiv(strval($collector->getJournals()->sum('transaction_amount')), strval($count));
// suggestion starts with the amount spent
$result['suggested'] = bcmul($result['spent'], '-1');
$result['suggested'] = bccomp($result['suggested'], $result['earned']) === 1 ? $result['earned'] : $result['suggested'];
// unless it's more than you earned. So min() of suggested/earned
$cache->store($result);
}
return view('budgets.info', compact('result', 'begin', 'currentEnd'));
}
/**
* @param Request $request
* @param JournalRepositoryInterface $repository
* @param string $moment
*
* @return View
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function noBudget(Request $request, JournalRepositoryInterface $repository, string $moment = '')
{
@@ -455,49 +522,6 @@ class BudgetController extends Controller
return view('budgets.income', compact('available', 'start', 'end'));
}
/**
* @param Collection $budgets
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
private function collectBudgetInformation(Collection $budgets, Carbon $start, Carbon $end): array
{
// get account information
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
$return = [];
/** @var Budget $budget */
foreach ($budgets as $budget) {
$budgetId = $budget->id;
$return[$budgetId] = [
'spent' => $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end),
'budgeted' => '0',
'currentRep' => false,
];
$budgetLimits = $this->repository->getBudgetLimits($budget, $start, $end);
$otherLimits = new Collection;
// 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]['otherLimits'] = $otherLimits;
}
return $return;
}
/**
* @param Budget $budget
* @param Carbon $start

View File

@@ -204,7 +204,8 @@ class CategoryController extends Controller
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount();
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount()
->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$journals = $collector->getPaginatedJournals();
$journals->setPath(route('categories.no-category'));
@@ -232,11 +233,12 @@ class CategoryController extends Controller
$end = null;
$periods = new Collection;
// prep for "all" view.
if ($moment === 'all') {
$subTitle = trans('firefly.all_journals_for_category', ['name' => $category->name]);
$start = $repository->firstUseDate($category);
$first = $repository->firstUseDate($category);
/** @var Carbon $start */
$start = is_null($first) ? new Carbon : $first;
$end = new Carbon;
}
@@ -254,7 +256,9 @@ class CategoryController extends Controller
// prep for current period
if (strlen($moment) === 0) {
/** @var Carbon $start */
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
/** @var Carbon $end */
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
$periods = $this->getPeriodOverview($category);
$subTitle = trans(
@@ -358,11 +362,11 @@ class CategoryController extends Controller
$end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($end, $range);
// count journals without budget in this period:
// count journals without category in this period:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
->withOpposingAccount();
->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$count = $collector->getJournals()->count();
@@ -420,13 +424,14 @@ class CategoryController extends Controller
$accountRepository = app(AccountRepositoryInterface::class);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$first = $repository->firstUseDate($category);
if ($first->year === 1900) {
if (is_null($first)) {
$first = new Carbon;
}
$range = Preferences::get('viewRange', '1M')->data;
$first = Navigation::startOfPeriod($first, $range);
$end = Navigation::endOfX(new Carbon, $range, null);
$entries = new Collection;
$count = 0;
// properties for entries with their amounts.
$cache = new CacheProperties();
@@ -438,7 +443,7 @@ class CategoryController extends Controller
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
while ($end >= $first) {
while ($end >= $first && $count < 90) {
$end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($end, $range);
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
@@ -466,6 +471,7 @@ class CategoryController extends Controller
]
);
$end = Navigation::subtractPeriod($end, $range, 1);
$count++;
}
$cache->store($entries);

View File

@@ -67,7 +67,7 @@ class CategoryController extends Controller
$start = $repository->firstUseDate($category);
if ($start->year === 1900) {
if (is_null($start)) {
$start = new Carbon;
}

View File

@@ -44,6 +44,8 @@ class Controller extends BaseController
protected $monthAndDayFormat;
/** @var string */
protected $monthFormat;
/** @var string */
protected $redirectUri = '/';
/**
* Controller constructor.
@@ -61,6 +63,7 @@ class Controller extends BaseController
View::share('IS_DEMO_SITE', $isDemoSite);
View::share('DEMO_USERNAME', env('DEMO_USERNAME', ''));
View::share('DEMO_PASSWORD', env('DEMO_PASSWORD', ''));
View::share('FF_VERSION', config('firefly.version'));
$this->middleware(
@@ -115,10 +118,10 @@ class Controller extends BaseController
{
$uri = strval(session($identifier));
if (!(strpos($identifier, 'delete') === false) && !(strpos($uri, '/show/') === false)) {
$uri = route('index');
$uri = $this->redirectUri;
}
if (!(strpos($uri, 'javascript') === false)) {
$uri = route('index');
if (!(strpos($uri, 'jscript') === false)) {
$uri = $this->redirectUri;
}
return $uri;

View File

@@ -46,7 +46,7 @@ class ExportController extends Controller
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-file-archive-o');
View::share('title', trans('firefly.export_data'));
View::share('title', trans('firefly.export_and_backup_data'));
return $next($request);
}

View File

@@ -131,7 +131,7 @@ class HomeController extends Controller
/** @var Carbon $end */
$end = session('end', Carbon::now()->endOfMonth());
$accounts = $repository->getAccountsById($frontPage->data);
$showDepositsFrontpage = Preferences::get('showDepositsFrontpage', false)->data;
$showDeps = Preferences::get('showDepositsFrontpage', false)->data;
// zero bills? Hide some elements from view.
/** @var BillRepositoryInterface $billRepository */
@@ -146,7 +146,7 @@ class HomeController extends Controller
}
return view(
'index', compact('count', 'subTitle', 'transactions', 'showDepositsFrontpage', 'billCount')
'index', compact('count', 'subTitle', 'transactions', 'showDeps', 'billCount')
);
}

View File

@@ -79,7 +79,6 @@ class BankController extends Controller
return redirect(route('import.bank.form', [$bank]));
}
$remoteAccounts = array_keys($remoteAccounts);
$class = config(sprintf('firefly.import_pre.%s', $bank));
// get import file

View File

@@ -0,0 +1,193 @@
<?php
/**
* BoxController.php
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Json;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Response;
/**
* Class BoxController
*
* @package FireflyIII\Http\Controllers\Json
*/
class BoxController extends Controller
{
/**
* @param BudgetRepositoryInterface $repository
*
* @return \Illuminate\Http\JsonResponse
*/
public function available(BudgetRepositoryInterface $repository)
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
$today = new Carbon;
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($today);
$cache->addProperty('box-available');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
// get available amount
$currency = app('amount')->getDefaultCurrency();
$available = $repository->getAvailableBudget($currency, $start, $end);
// get spent amount:
$budgets = $repository->getActiveBudgets();
$budgetInformation = $repository->collectBudgetInformation($budgets, $start, $end);
$spent = strval(array_sum(array_column($budgetInformation, 'spent')));
$left = bcadd($available, $spent);
// left less than zero? then it's zero:
if (bccomp($left, '0') === -1) {
$left = '0';
}
$days = $today->diffInDays($end) + 1;
$perDay = '0';
if ($days !== 0) {
$perDay = bcdiv($left, strval($days));
}
$return = [
'perDay' => app('amount')->formatAnything($currency, $perDay, false),
'left' => app('amount')->formatAnything($currency, $left, false),
];
$cache->store($return);
return Response::json($return);
}
/**
* @return \Illuminate\Http\JsonResponse
*/
public function balance()
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('box-balance');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
// try a collector for income:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
$income = strval($collector->getJournals()->sum('transaction_amount'));
$currency = app('amount')->getDefaultCurrency();
// expense:
// try a collector for expenses:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
$expense = strval($collector->getJournals()->sum('transaction_amount'));
$response = [
'income' => app('amount')->formatAnything($currency, $income, false),
'expense' => app('amount')->formatAnything($currency, $expense, false),
'combined' => app('amount')->formatAnything($currency, bcadd($income, $expense), false),
];
$cache->store($response);
return Response::json($response);
}
/**
* @param BillRepositoryInterface $repository
*
* @return \Illuminate\Http\JsonResponse
*/
public function bills(BillRepositoryInterface $repository)
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('box-bills');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
/*
* Since both this method and the chart use the exact same data, we can suffice
* with calling the one method in the bill repository that will get this amount.
*/
$paidAmount = bcmul($repository->getBillsPaidInRange($start, $end), '-1');
$unpaidAmount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
$currency = app('amount')->getDefaultCurrency();
$return = [
'paid' => app('amount')->formatAnything($currency, $paidAmount, false),
'unpaid' => app('amount')->formatAnything($currency, $unpaidAmount, false),
];
$cache->store($return);
return Response::json($return);
}
/**
* @param AccountRepositoryInterface $repository
*
* @return \Illuminate\Http\JsonResponse
*/
public function netWorth(AccountRepositoryInterface $repository)
{
$today = new Carbon(date('Y-m-d')); // needed so its per day.
$cache = new CacheProperties;
$cache->addProperty($today);
$cache->addProperty('box-net-worth');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$currency = app('amount')->getDefaultCurrency();
$balances = app('steam')->balancesByAccounts($accounts, $today);
$sum = '0';
foreach ($balances as $entry) {
$sum = bcadd($sum, $entry);
}
$return = [
'net_worth' => app('amount')->formatAnything($currency, $sum, false),
];
$cache->store($return);
return Response::json($return);
}
}

View File

@@ -60,114 +60,6 @@ class JsonController extends Controller
return Response::json(['html' => $view]);
}
/**
* @param BillRepositoryInterface $repository
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function boxBillsPaid(BillRepositoryInterface $repository)
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
/*
* Since both this method and the chart use the exact same data, we can suffice
* with calling the one method in the bill repository that will get this amount.
*/
$amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
$amount = bcmul($amount, '-1');
$currency = Amount::getDefaultCurrency();
$data = ['box' => 'bills-paid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
return Response::json($data);
}
/**
* @param BillRepositoryInterface $repository
*
* @return \Illuminate\Http\JsonResponse
*/
public function boxBillsUnpaid(BillRepositoryInterface $repository)
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
$amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
$currency = Amount::getDefaultCurrency();
$data = ['box' => 'bills-unpaid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
return Response::json($data);
}
/**
* @return \Illuminate\Http\JsonResponse
* @internal param AccountTaskerInterface $accountTasker
* @internal param AccountRepositoryInterface $repository
*
*/
public function boxIn()
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
// works for json too!
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('box-in');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
// try a collector for income:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
$amount = strval($collector->getJournals()->sum('transaction_amount'));
$currency = Amount::getDefaultCurrency();
$data = ['box' => 'in', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
$cache->store($data);
return Response::json($data);
}
/**
* @return \Symfony\Component\HttpFoundation\Response
* @internal param AccountTaskerInterface $accountTasker
* @internal param AccountRepositoryInterface $repository
*
*/
public function boxOut()
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
// works for json too!
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('box-out');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
// try a collector for expenses:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
$amount = strval($collector->getJournals()->sum('transaction_amount'));
$currency = Amount::getDefaultCurrency();
$data = ['box' => 'out', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
$cache->store($data);
return Response::json($data);
}
/**
* @param BudgetRepositoryInterface $repository
*

View File

@@ -92,7 +92,7 @@ class PreferencesController extends Controller
$language = Preferences::get('language', config('firefly.default_language', 'en_US'))->data;
$transactionPageSize = Preferences::get('transactionPageSize', 50)->data;
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
$showDepositsFrontpage = Preferences::get('showDepositsFrontpage', false)->data;
$showDeps = Preferences::get('showDepositsFrontpage', false)->data;
$fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
$fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr;
$tjOptionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
@@ -105,7 +105,7 @@ class PreferencesController extends Controller
compact(
'language', 'accounts', 'frontPageAccounts', 'tjOptionalFields',
'viewRange', 'customFiscalYear', 'transactionPageSize', 'fiscalYearStart', 'is2faEnabled',
'has2faSecret', 'showIncomplete', 'showDepositsFrontpage'
'has2faSecret', 'showIncomplete', 'showDeps'
)
);
}

View File

@@ -13,14 +13,20 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use Auth;
use FireflyIII\Events\UserChangedEmail;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Exceptions\ValidationException;
use FireflyIII\Http\Middleware\IsLimitedUser;
use FireflyIII\Http\Requests\DeleteAccountFormRequest;
use FireflyIII\Http\Requests\EmailFormRequest;
use FireflyIII\Http\Requests\ProfileFormRequest;
use FireflyIII\Models\Preference;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Hash;
use Log;
use Preferences;
use Session;
use View;
@@ -47,10 +53,23 @@ class ProfileController extends Controller
return $next($request);
}
);
$this->middleware(IsLimitedUser::class);
$this->middleware(IsLimitedUser::class)->except(['confirmEmailChange', 'undoEmailChange']);
}
/**
* @return View
*/
public function changeEmail()
{
$title = auth()->user()->email;
$email = auth()->user()->email;
$subTitle = strval(trans('firefly.change_your_email'));
$subTitleIcon = 'fa-envelope';
return view('profile.change-email', compact('title', 'subTitle', 'subTitleIcon', 'email'));
}
/**
* @return View
*/
@@ -63,6 +82,37 @@ class ProfileController extends Controller
return view('profile.change-password', compact('title', 'subTitle', 'subTitleIcon'));
}
/**
* @param string $token
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws FireflyException
*/
public function confirmEmailChange(string $token)
{
// find preference with this token value.
$set = Preferences::findByName('email_change_confirm_token');
$user = null;
/** @var Preference $preference */
foreach ($set as $preference) {
if ($preference->data === $token) {
$user = $preference->user;
}
}
// update user to clear blocked and blocked_code.
if (is_null($user)) {
throw new FireflyException('Invalid token.');
}
$user->blocked = 0;
$user->blocked_code = '';
$user->save();
// return to login.
Session::flash('success', strval(trans('firefly.login_with_new_email')));
return redirect(route('login'));
}
/**
* @return View
*/
@@ -84,7 +134,57 @@ class ProfileController extends Controller
$subTitle = auth()->user()->email;
$userId = auth()->user()->id;
return view('profile.index', compact('subTitle', 'userId'));
// get access token or create one.
$accessToken = Preferences::get('access_token', null);
if (is_null($accessToken)) {
$token = auth()->user()->generateAccessToken();
$accessToken = Preferences::set('access_token', $token);
}
return view('profile.index', compact('subTitle', 'userId', 'accessToken'));
}
/**
* @param EmailFormRequest $request
* @param UserRepositoryInterface $repository
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function postChangeEmail(EmailFormRequest $request, UserRepositoryInterface $repository)
{
/** @var User $user */
$user = auth()->user();
$newEmail = $request->string('email');
$oldEmail = $user->email;
if ($newEmail === $user->email) {
Session::flash('error', strval(trans('firefly.email_not_changed')));
return redirect(route('profile.change-email'))->withInput();
}
$existing = $repository->findByEmail($newEmail);
if (!is_null($existing)) {
// force user logout.
$this->guard()->logout();
$request->session()->invalidate();
Session::flash('success', strval(trans('firefly.email_changed')));
return redirect(route('index'));
}
// now actually update user:
$repository->changeEmail($user, $newEmail);
// call event.
$ipAddress = $request->ip();
event(new UserChangedEmail($user, $newEmail, $oldEmail, $ipAddress));
// force user logout.
Auth::guard()->logout();
$request->session()->invalidate();
Session::flash('success', strval(trans('firefly.email_changed')));
return redirect(route('index'));
}
/**
@@ -140,6 +240,64 @@ class ProfileController extends Controller
return redirect(route('index'));
}
/**
*
*/
function regenerate()
{
$token = auth()->user()->generateAccessToken();
Preferences::set('access_token', $token);
Session::flash('success', strval(trans('firefly.token_regenerated')));
return redirect(route('profile.index'));
}
/**
* @param string $token
* @param string $hash
*
* @throws FireflyException
*/
public function undoEmailChange(string $token, string $hash)
{
// find preference with this token value.
$set = Preferences::findByName('email_change_undo_token');
$user = null;
/** @var Preference $preference */
foreach ($set as $preference) {
if ($preference->data === $token) {
$user = $preference->user;
}
}
if (is_null($user)) {
throw new FireflyException('Invalid token.');
}
// found user.
// which email address to return to?
$set = Preferences::beginsWith($user, 'previous_email_');
$match = null;
foreach ($set as $entry) {
$hashed = hash('sha256', $entry->data);
if ($hashed === $hash) {
$match = $entry->data;
break;
}
}
if (is_null($match)) {
throw new FireflyException('Invalid token.');
}
// change user back
$user->email = $match;
$user->blocked = 0;
$user->blocked_code = '';
$user->save();
// return to login.
Session::flash('success', strval(trans('firefly.login_with_old_email')));
return redirect(route('login'));
}
/**
* @param User $user

View File

@@ -27,7 +27,7 @@ use FireflyIII\Models\RuleTrigger;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Rules\TransactionMatcher;
use FireflyIII\TransactionRules\TransactionMatcher;
use Illuminate\Http\Request;
use Preferences;
use Response;

View File

@@ -51,7 +51,7 @@ class SearchController extends Controller
*/
public function index(Request $request, SearchInterface $searcher)
{
$fullQuery = $request->get('q');
$fullQuery = strval($request->get('q'));
// parse search terms:
$searcher->parseQuery($fullQuery);

View File

@@ -53,6 +53,7 @@ class TagController extends Controller
{
parent::__construct();
View::share('hideTags', true);
$this->redirectUri = route('tags.index');
$this->middleware(
function ($request, $next) {
@@ -194,17 +195,13 @@ class TagController extends Controller
$end = null;
$periods = new Collection;
$apiKey = env('GOOGLE_MAPS_API_KEY', '');
$sum = '0';
$path = route('tags.show', [$tag->id]);
// prep for "all" view.
if ($moment === 'all') {
$subTitle = trans('firefly.all_journals_for_tag', ['tag' => $tag->tag]);
$start = $repository->firstUseDate($tag);
$end = new Carbon;
$sum = $repository->sumOfTag($tag, null, null);
$result = $repository->resultOfTag($tag, null, null);
$path = route('tags.show', [$tag->id, 'all']);
}
@@ -218,18 +215,16 @@ class TagController extends Controller
'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$periods = $this->getPeriodOverview($tag);
$sum = $repository->sumOfTag($tag, $start, $end);
$result = $repository->resultOfTag($tag, $start, $end);
$path = route('tags.show', [$tag->id, $moment]);
}
// prep for current period
if (strlen($moment) === 0) {
/** @var Carbon $start */
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
/** @var Carbon $end */
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
$periods = $this->getPeriodOverview($tag);
$sum = $repository->sumOfTag($tag, $start, $end);
$result = $repository->resultOfTag($tag, $start, $end);
$subTitle = trans(
'firefly.journals_in_period_for_tag',
['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
@@ -243,8 +238,9 @@ class TagController extends Controller
$journals = $collector->getPaginatedJournals();
$journals->setPath($path);
$sums = $repository->sumsOfTag($tag, $start, $end);
return view('tags.show', compact('apiKey', 'tag', 'result', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end', 'moment'));
return view('tags.show', compact('apiKey', 'tag', 'sums', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'start', 'end', 'moment'));
}
/**

View File

@@ -85,7 +85,7 @@ class ConvertController extends Controller
// cannot convert split.
if ($journal->transactions()->count() > 2) {
Session::flash('error', trans('firefly.cannot_convert_split_journl'));
Session::flash('error', trans('firefly.cannot_convert_split_journal'));
return redirect(route('transactions.show', [$journal->id]));
}
@@ -134,7 +134,7 @@ class ConvertController extends Controller
}
if ($journal->transactions()->count() > 2) {
Session::flash('error', trans('firefly.cannot_convert_split_journl'));
Session::flash('error', trans('firefly.cannot_convert_split_journal'));
return redirect(route('transactions.show', [$journal->id]));
}
@@ -185,7 +185,7 @@ class ConvertController extends Controller
case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL:
case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL:
// three and five
if ($data['destination_account_expense'] === '') {
if ($data['destination_account_expense'] === '' || is_null($data['destination_account_expense'])) {
// destination is a cash account.
$destination = $accountRepository->getCashAccount();
@@ -232,7 +232,7 @@ class ConvertController extends Controller
case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT:
case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT:
if ($data['source_account_revenue'] === '') {
if ($data['source_account_revenue'] === '' || is_null($data['source_account_revenue'])) {
// destination is a cash account.
$destination = $accountRepository->getCashAccount();

View File

@@ -95,6 +95,11 @@ class LinkController extends Controller
JournalLinkRequest $request, LinkTypeRepositoryInterface $repository, JournalRepositoryInterface $journalRepository, TransactionJournal $journal
) {
$linkInfo = $request->getLinkInfo();
if ($linkInfo['transaction_journal_id'] === 0) {
Session::flash('error', trans('firefly.invalid_link_selection'));
return redirect(route('transactions.show', [$journal->id]));
}
$linkType = $repository->find($linkInfo['link_type_id']);
$other = $journalRepository->find($linkInfo['transaction_journal_id']);
$alreadyLinked = $repository->findLink($journal, $other);

View File

@@ -184,6 +184,7 @@ class SplitController extends Controller
*/
private function arrayFromInput(SplitJournalFormRequest $request): array
{
$tags = is_null($request->get('tags')) ? '' : $request->get('tags');
$array = [
'journal_description' => $request->get('journal_description'),
'journal_source_account_id' => $request->get('journal_source_account_id'),
@@ -200,7 +201,7 @@ class SplitController extends Controller
'invoice_date' => $request->get('invoice_date'),
'internal_reference' => $request->get('internal_reference'),
'notes' => $request->get('notes'),
'tags' => explode(',', $request->get('tags')),
'tags' => explode(',', $tags),
// transactions.
'transactions' => $this->getTransactionDataFromRequest($request),

View File

@@ -1,14 +1,15 @@
<?php
declare(strict_types=1);
/**
* Kernel.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Http;
@@ -21,42 +22,24 @@ use FireflyIII\Http\Middleware\Range;
use FireflyIII\Http\Middleware\RedirectIfAuthenticated;
use FireflyIII\Http\Middleware\RedirectIfTwoFactorAuthenticated;
use FireflyIII\Http\Middleware\Sandstorm;
use FireflyIII\Http\Middleware\StartFireflySession;
use FireflyIII\Http\Middleware\TrimStrings;
use FireflyIII\Http\Middleware\TrustProxies;
use FireflyIII\Http\Middleware\VerifyCsrfToken;
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
use Illuminate\Auth\Middleware\Authorize;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
/**
* Class Kernel
*
* @package FireflyIII\Http
*/
class Kernel extends HttpKernel
{
/**
* The bootstrap classes for the application.
*
* Next upgrade verify these are the same.
*
* @var array
*/
protected $bootstrappers
= [
'Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables',
'Illuminate\Foundation\Bootstrap\LoadConfiguration',
'Illuminate\Foundation\Bootstrap\HandleExceptions',
'Illuminate\Foundation\Bootstrap\RegisterFacades',
'Illuminate\Foundation\Bootstrap\RegisterProviders',
'Illuminate\Foundation\Bootstrap\BootProviders',
];
/**
* The application's global HTTP middleware stack.
*
@@ -67,6 +50,10 @@ class Kernel extends HttpKernel
protected $middleware
= [
CheckForMaintenanceMode::class,
ValidatePostSize::class,
TrimStrings::class,
ConvertEmptyStringsToNull::class,
TrustProxies::class,
];
/**
@@ -83,7 +70,7 @@ class Kernel extends HttpKernel
Sandstorm::class,
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
StartSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
@@ -95,7 +82,7 @@ class Kernel extends HttpKernel
Sandstorm::class,
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
StartSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
@@ -108,7 +95,7 @@ class Kernel extends HttpKernel
Sandstorm::class,
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
StartSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
@@ -123,7 +110,7 @@ class Kernel extends HttpKernel
Sandstorm::class,
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
StartSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
@@ -138,7 +125,7 @@ class Kernel extends HttpKernel
Sandstorm::class,
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
StartSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
@@ -156,7 +143,7 @@ class Kernel extends HttpKernel
Sandstorm::class,
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
StartSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
@@ -174,6 +161,7 @@ class Kernel extends HttpKernel
],
];
/**
* The application's route middleware.
*
@@ -189,6 +177,5 @@ class Kernel extends HttpKernel
'can' => Authorize::class,
'guest' => RedirectIfAuthenticated::class,
'throttle' => ThrottleRequests::class,
'range' => Range::class,
];
}

View File

@@ -44,8 +44,13 @@ class Authenticate
return redirect()->guest('login');
}
if (intval(auth()->user()->blocked) === 1) {
$message = strval(trans('firefly.block_account_logout'));
if (auth()->user()->blocked_code === 'email_changed') {
$message = strval(trans('firefly.email_changed_logout'));
}
Session::flash('logoutMessage', $message);
Auth::guard($guard)->logout();
Session::flash('logoutMessage', trans('firefly.block_account_logout'));
return redirect()->guest('login');
}

View File

@@ -1,25 +1,21 @@
<?php
declare(strict_types=1);
/**
* EncryptCookies.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as BaseEncrypter;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
/**
* Class EncryptCookies
*
* @package FireflyIII\Http\Middleware
*/
class EncryptCookies extends BaseEncrypter
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.

View File

@@ -98,7 +98,12 @@ class Range
$locale = array_map('trim', $locale);
setlocale(LC_TIME, $locale);
setlocale(LC_MONETARY, $locale);
$moneyResult = setlocale(LC_MONETARY, $locale);
// send error to view if could not set money format
if ($moneyResult === false) {
View::share('invalidMonetaryLocale', true);
}
// save some formats:

View File

@@ -1,25 +1,21 @@
<?php
declare(strict_types=1);
/**
* RedirectIfAuthenticated.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
/**
* Class RedirectIfAuthenticated
*
* @package FireflyIII\Http\Middleware
*/
class RedirectIfAuthenticated
{
/**
@@ -34,7 +30,7 @@ class RedirectIfAuthenticated
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/');
return redirect('/home');
}
return $next($request);

View File

@@ -1,54 +0,0 @@
<?php
/**
* StartFireflySession.php
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Middleware;
use Illuminate\Http\Request;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Session\SessionManager;
/**
* Class StartFireflySession
*
* @package FireflyIII\Http\Middleware
*/
class StartFireflySession extends StartSession
{
/**
* Create a new session middleware.
*
* @param \Illuminate\Session\SessionManager $manager
*/
public function __construct(SessionManager $manager)
{
parent::__construct($manager);
}
/**
* Store the current URL for the request if necessary.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Contracts\Session\Session $session
*
* @return void
*/
protected function storeCurrentUrl(Request $request, $session)
{
$fullUrl = $request->fullUrl();
if ($request->method() === 'GET' && $request->route() && !$request->ajax()) {
if (strpos($fullUrl, '/javascript/') === false) {
$session->setPreviousUrl($fullUrl);
}
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* TrimStrings.php
* 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.
*/
namespace FireflyIII\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array
*/
protected $except
= [
'password',
'password_confirmation',
];
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* TrustProxies.php
* 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.
*/
namespace FireflyIII\Http\Middleware;
use Fideloper\Proxy\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The current proxy header mappings.
*
* @var array
*/
protected $headers
= [
Request::HEADER_FORWARDED => 'FORWARDED',
Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
];
/**
* The trusted proxies for this application.
*
* @var array
*/
protected $proxies;
}

View File

@@ -1,27 +1,21 @@
<?php
declare(strict_types=1);
/**
* VerifyCsrfToken.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Middleware;
use Carbon\Carbon;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
use Symfony\Component\HttpFoundation\Cookie;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
/**
* Class VerifyCsrfToken
*
* @package FireflyIII\Http\Middleware
*/
class VerifyCsrfToken extends BaseVerifier
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
@@ -32,26 +26,4 @@ class VerifyCsrfToken extends BaseVerifier
= [
//
];
/**
* Add the CSRF token to the response cookies.
*
* @param \Illuminate\Http\Request $request
* @param \Symfony\Component\HttpFoundation\Response $response
*
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function addCookieToResponse($request, $response)
{
$config = config('session');
$response->headers->setCookie(
new Cookie(
'XSRF-TOKEN', $request->session()->token(), Carbon::now()->getTimestamp() + 60 * $config['lifetime'],
$config['path'], $config['domain'], $config['secure'], true
)
);
return $response;
}
}

View File

@@ -75,13 +75,13 @@ class AccountFormRequest extends Request
return [
'id' => $idRule,
'name' => $nameRule,
'openingBalance' => 'numeric|required_with:openingBalanceDate',
'openingBalanceDate' => 'date|required_with:openingBalance',
'iban' => 'iban',
'BIC' => 'bic',
'virtualBalance' => 'numeric',
'openingBalance' => 'numeric|required_with:openingBalanceDate|nullable',
'openingBalanceDate' => 'date|required_with:openingBalance|nullable',
'iban' => 'iban|nullable',
'BIC' => 'bic|nullable',
'virtualBalance' => 'numeric|nullable',
'currency_id' => 'exists:transaction_currencies,id',
'accountNumber' => 'between:1,255|uniqueAccountNumberForUser',
'accountNumber' => 'between:1,255|uniqueAccountNumberForUser|nullable',
'accountRole' => 'in:' . $accountRoles,
'active' => 'boolean',
'ccType' => 'in:' . $ccPaymentTypes,

View File

@@ -47,11 +47,11 @@ class AttachmentFormRequest extends Request
*/
public function rules()
{
// fixed
return [
'title' => 'between:1,255',
'description' => 'between:1,65536',
'notes' => 'between:1,65536',
'title' => 'between:1,255|nullable',
'description' => 'between:1,65536|nullable',
'notes' => 'between:1,65536|nullable',
];
}
}

View File

@@ -61,7 +61,7 @@ class BillFormRequest extends Request
$nameRule .= ',' . intval($this->get('id'));
$matchRule .= ',' . intval($this->get('id'));
}
// is OK
$rules = [
'name' => $nameRule,
'match' => $matchRule,

View File

@@ -47,6 +47,7 @@ class BudgetFormRequest extends Request
*/
public function rules()
{
// fixed
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$nameRule = 'required|between:1,100|uniqueObjectForUser:budgets,name';

View File

@@ -35,6 +35,7 @@ class BudgetIncomeRequest extends Request
*/
public function rules()
{
// fixed
return [
'amount' => 'numeric|required|min:0',
'start' => 'required|date|before:end',

View File

@@ -54,6 +54,7 @@ class CategoryFormRequest extends Request
$nameRule = 'required|between:1,100|uniqueObjectForUser:categories,name,' . intval($this->get('id'));
}
// fixed
return [
'name' => $nameRule,
];

View File

@@ -46,6 +46,7 @@ class ConfigurationRequest extends Request
*/
public function rules()
{
// fixed
$rules = [
'single_user_mode' => 'between:0,1|numeric',
'is_demo_site' => 'between:0,1|numeric',

View File

@@ -48,7 +48,7 @@ class CurrencyFormRequest extends Request
*/
public function rules()
{
// fixed
$rules = [
'name' => 'required|max:48|min:1|unique:transaction_currencies,name',
'code' => 'required|min:3|max:3|unique:transaction_currencies,code',

View File

@@ -35,6 +35,7 @@ class DeleteAccountFormRequest extends Request
*/
public function rules()
{
// fixed
return [
'password' => 'required',
];

View File

@@ -0,0 +1,42 @@
<?php
/**
* EmailFormRequest.php
* 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.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Requests;
/**
* Class EmailFormRequest
*
*
* @package FireflyIII\Http\Requests
*/
class EmailFormRequest extends Request
{
/**
* @return bool
*/
public function authorize()
{
// Only allow logged in users
return auth()->check();
}
/**
* @return array
*/
public function rules()
{
// fixed
return [
'email' => 'required|email',
];
}
}

View File

@@ -42,6 +42,8 @@ class ExportFormRequest extends Request
$today = Carbon::create()->addDay()->format('Y-m-d');
$formats = join(',', array_keys(config('firefly.export_formats')));
// fixed
return [
'export_start_range' => 'required|date|after:' . $first,
'export_end_range' => 'required|date|before:' . $today,

View File

@@ -35,6 +35,7 @@ class ImportUploadRequest extends Request
*/
public function rules()
{
// fixed
$types = array_keys(config('firefly.import_formats'));
return [

View File

@@ -88,29 +88,29 @@ class JournalFormRequest extends Request
'date' => 'required|date',
// then, custom fields:
'interest_date' => 'date',
'book_date' => 'date',
'process_date' => 'date',
'due_date' => 'date',
'payment_date' => 'date',
'invoice_date' => 'date',
'internal_reference' => 'min:1,max:255',
'notes' => 'min:1,max:50000',
'interest_date' => 'date|nullable',
'book_date' => 'date|nullable',
'process_date' => 'date|nullable',
'due_date' => 'date|nullable',
'payment_date' => 'date|nullable',
'invoice_date' => 'date|nullable',
'internal_reference' => 'min:1,max:255|nullable',
'notes' => 'min:1,max:50000|nullable',
// and then transaction rules:
'description' => 'required|between:1,255',
'amount' => 'numeric|required|more:0',
'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id',
'category' => 'between:1,255',
'source_account_id' => 'numeric|belongsToUser:accounts,id',
'source_account_name' => 'between:1,255',
'destination_account_id' => 'numeric|belongsToUser:accounts,id',
'destination_account_name' => 'between:1,255',
'piggy_bank_id' => 'between:1,255',
'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable',
'category' => 'between:1,255|nullable',
'source_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
'source_account_name' => 'between:1,255|nullable',
'destination_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
'destination_account_name' => 'between:1,255|nullable',
'piggy_bank_id' => 'between:1,255|nullable',
// foreign currency amounts
'native_amount' => 'numeric|more:0',
'source_amount' => 'numeric|more:0',
'destination_amount' => 'numeric',
'native_amount' => 'numeric|more:0|nullable',
'source_amount' => 'numeric|more:0|nullable',
'destination_amount' => 'numeric|more:0|nullable',
];
// some rules get an upgrade depending on the type of data:
@@ -133,10 +133,10 @@ class JournalFormRequest extends Request
switch ($what) {
case strtolower(TransactionType::WITHDRAWAL):
$rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
$rules['destination_account_name'] = 'between:1,255';
$rules['destination_account_name'] = 'between:1,255|nullable';
break;
case strtolower(TransactionType::DEPOSIT):
$rules['source_account_name'] = 'between:1,255';
$rules['source_account_name'] = 'between:1,255|nullable';
$rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
break;
case strtolower(TransactionType::TRANSFER):

View File

@@ -65,6 +65,7 @@ class JournalLinkRequest extends Request
}
$string = join(',', $combinations);
// fixed
return [
'link_type' => sprintf('required|in:%s', $string),
'link_other' => 'belongsToUser:transaction_journals',

View File

@@ -36,6 +36,8 @@ class LinkTypeFormRequest extends Request
*/
public function rules()
{
// fixed
/** @var LinkTypeRepositoryInterface $repository */
$repository = app(LinkTypeRepositoryInterface::class);
$nameRule = 'required|min:1|unique:link_types,name';

View File

@@ -35,6 +35,7 @@ class MassDeleteJournalRequest extends Request
*/
public function rules()
{
// fixed
return [
'confirm_mass_delete.*' => 'required|belongsToUser:transaction_journals,id',
];

View File

@@ -35,6 +35,8 @@ class MassEditJournalRequest extends Request
*/
public function rules()
{
// fixed
return [
'description.*' => 'required|min:1,max:255',
'source_account_id.*' => 'numeric|belongsToUser:accounts,id',

View File

@@ -35,6 +35,7 @@ class NewUserFormRequest extends Request
*/
public function rules()
{
// fixed
return [
'bank_name' => 'required|between:1,200',
'bank_balance' => 'required|numeric',

View File

@@ -54,7 +54,6 @@ class PiggyBankFormRequest extends Request
{
$nameRule = 'required|between:1,255|uniquePiggyBankForUser';
$targetDateRule = 'date';
if (intval($this->get('id'))) {
$nameRule = 'required|between:1,255|uniquePiggyBankForUser:' . intval($this->get('id'));
}
@@ -66,7 +65,7 @@ class PiggyBankFormRequest extends Request
'targetamount' => 'required|numeric|more:0',
'amount_currency_id_targetamount' => 'required|exists:transaction_currencies,id',
'startdate' => 'date',
'targetdate' => $targetDateRule,
'targetdate' => 'date|nullable',
'order' => 'integer|min:1',
];

View File

@@ -35,6 +35,7 @@ class ProfileFormRequest extends Request
*/
public function rules()
{
// fixed
return [
'current_password' => 'required',
'new_password' => 'required|confirmed|secure_password',

View File

@@ -44,6 +44,7 @@ class ReportFormRequest extends Request
*/
public function getAccountList(): Collection
{
// fixed
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$set = $this->get('accounts');

View File

@@ -72,7 +72,7 @@ class RuleFormRequest extends Request
}
$rules = [
'title' => $titleRule,
'description' => 'between:1,5000',
'description' => 'between:1,5000|nullable',
'stop_processing' => 'boolean',
'rule_group_id' => 'required|belongsToUser:rule_groups',
'trigger' => 'required|in:store-journal,update-journal',

View File

@@ -48,6 +48,7 @@ class RuleGroupFormRequest extends Request
*/
public function rules()
{
// fixed
/** @var RuleGroupRepositoryInterface $repository */
$repository = app(RuleGroupRepositoryInterface::class);
$titleRule = 'required|between:1,100|uniqueObjectForUser:rule_groups,title';
@@ -57,7 +58,7 @@ class RuleGroupFormRequest extends Request
return [
'title' => $titleRule,
'description' => 'between:1,5000',
'description' => 'between:1,5000|nullable',
];
}
}

View File

@@ -37,8 +37,8 @@ class SelectTransactionsRequest extends Request
*/
public function rules()
{
// fixed
$sessionFirst = clone session('first');
$first = $sessionFirst->subDay()->format('Y-m-d');
$today = Carbon::create()->addDay()->format('Y-m-d');

View File

@@ -68,16 +68,16 @@ class SplitJournalFormRequest extends Request
'journal_source_account_name.*' => 'between:1,255',
'journal_currency_id' => 'required|exists:transaction_currencies,id',
'date' => 'required|date',
'interest_date' => 'date',
'book_date' => 'date',
'process_date' => 'date',
'interest_date' => 'date|nullable',
'book_date' => 'date|nullable',
'process_date' => 'date|nullable',
'transactions.*.description' => 'required|between:1,255',
'transactions.*.destination_account_id' => 'numeric|belongsToUser:accounts,id',
'transactions.*.destination_account_name' => 'between:1,255',
'transactions.*.destination_account_name' => 'between:1,255|nullable',
'transactions.*.amount' => 'required|numeric',
'transactions.*.budget_id' => 'belongsToUser:budgets,id',
'transactions.*.category' => 'between:1,255',
'transactions.*.piggy_bank_id' => 'between:1,255',
'transactions.*.category' => 'between:1,255|nullable',
'transactions.*.piggy_bank_id' => 'between:1,255|nullable',
];
}

View File

@@ -77,11 +77,11 @@ class TagFormRequest extends Request
return [
'tag' => $tagRule,
'id' => $idRule,
'description' => 'min:1',
'date' => 'date',
'latitude' => 'numeric|min:-90|max:90',
'longitude' => 'numeric|min:-90|max:90',
'zoomLevel' => 'numeric|min:0|max:80',
'description' => 'min:1|nullable',
'date' => 'date|nullable',
'latitude' => 'numeric|min:-90|max:90|nullable',
'longitude' => 'numeric|min:-90|max:90|nullable',
'zoomLevel' => 'numeric|min:0|max:80|nullable',
];
}
}

View File

@@ -35,7 +35,7 @@ class TestRuleFormRequest extends Request
*/
public function rules()
{
// fixed
$validTriggers = array_keys(config('firefly.rule-triggers'));
$rules = [
'rule-trigger.*' => 'required|min:1|in:' . join(',', $validTriggers),

Some files were not shown because too many files have changed in this diff Show More