Merge branch 'develop' into 5.8-dev

This commit is contained in:
James Cole 2022-10-01 19:06:55 +02:00
commit 5f11272d49
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
25 changed files with 367 additions and 122 deletions

View File

@ -0,0 +1,93 @@
<?php
/*
* PurgeController.php
* Copyright (c) 2022 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Api\V1\Controllers\Data;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\Recurrence;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Http\JsonResponse;
class PurgeController extends Controller
{
/**
* @return JsonResponse
*/
public function purge(): JsonResponse
{
$user = auth()->user();
// some manual code, too lazy to call all repositories.
//,transactions,withdrawals,deposits,transfers';
// budgets:
Budget::whereUserId($user->id)->onlyTrashed()->forceDelete();
// bills
Bill::whereUserId($user->id)->onlyTrashed()->forceDelete();
// piggies
$set = PiggyBank::leftJoin('accounts','accounts.id','piggy_banks.account_id')
->where('accounts.user_id', $user->id)->onlyTrashed()->get(['piggy_banks.*']);
/** @var PiggyBank $piggy */
foreach($set as $piggy) {
$piggy->forceDelete();
}
// rule group
RuleGroup::whereUserId($user->id)->onlyTrashed()->forceDelete();
// rules
Rule::whereUserId($user->id)->onlyTrashed()->forceDelete();
// recurring transactions
Recurrence::whereUserId($user->id)->onlyTrashed()->forceDelete();
// categories
Category::whereUserId($user->id)->onlyTrashed()->forceDelete();
// tags
Tag::whereUserId($user->id)->onlyTrashed()->forceDelete();
// accounts
Account::whereUserId($user->id)->onlyTrashed()->forceDelete();
// transaction groups
TransactionGroup::whereUserId($user->id)->onlyTrashed()->forceDelete();
// transaction journals
TransactionJournal::whereUserId($user->id)->onlyTrashed()->forceDelete();
return response()->json([], 204);
}
}

View File

@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1);
/* /*
* InvitationCreated.php * InvitationCreated.php
* Copyright (c) 2022 james@firefly-iii.org * Copyright (c) 2022 james@firefly-iii.org

View File

@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1);
/* /*
* NewVersionAvailable.php * NewVersionAvailable.php
* Copyright (c) 2022 james@firefly-iii.org * Copyright (c) 2022 james@firefly-iii.org

View File

@ -27,12 +27,14 @@ use Carbon\Carbon;
use Database\Seeders\ExchangeRateSeeder; use Database\Seeders\ExchangeRateSeeder;
use Exception; use Exception;
use FireflyIII\Events\ActuallyLoggedIn; use FireflyIII\Events\ActuallyLoggedIn;
use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\DetectedNewIPAddress; use FireflyIII\Events\DetectedNewIPAddress;
use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword; use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\UserChangedEmail; use FireflyIII\Events\UserChangedEmail;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Mail\ConfirmEmailChangeMail; use FireflyIII\Mail\ConfirmEmailChangeMail;
use FireflyIII\Mail\InvitationMail;
use FireflyIII\Mail\UndoEmailChangeMail; use FireflyIII\Mail\UndoEmailChangeMail;
use FireflyIII\Models\GroupMembership; use FireflyIII\Models\GroupMembership;
use FireflyIII\Models\UserGroup; use FireflyIII\Models\UserGroup;
@ -77,6 +79,23 @@ class UserEventHandler
} }
} }
/**
* @param InvitationCreated $event
* @return void
*/
public function sendRegistrationInvite(InvitationCreated $event): void
{
$invitee = $event->invitee->email;
$admin = $event->invitee->user->email;
$url = route('invite', [$event->invitee->invite_code]);
try {
Mail::to($invitee)->send(new InvitationMail($invitee, $admin, $url));
} catch (Exception $e) { // @phpstan-ignore-line
Log::error($e->getMessage());
}
}
/** /**
* @param RegisteredUser $event * @param RegisteredUser $event
* @return bool * @return bool

View File

@ -237,9 +237,6 @@ trait MetaCollection
return $this; return $this;
} }
/** /**
* @param string $url * @param string $url
* @return GroupCollectorInterface * @return GroupCollectorInterface
@ -269,9 +266,6 @@ trait MetaCollection
return $this; return $this;
} }
/** /**
* @param string $url * @param string $url
* @return GroupCollectorInterface * @return GroupCollectorInterface

View File

@ -411,9 +411,6 @@ interface GroupCollectorInterface
* @return GroupCollectorInterface * @return GroupCollectorInterface
*/ */
public function externalIdDoesNotContain(string $externalId): GroupCollectorInterface; public function externalIdDoesNotContain(string $externalId): GroupCollectorInterface;
/** /**
* @param string $externalId * @param string $externalId
* @return GroupCollectorInterface * @return GroupCollectorInterface
@ -437,9 +434,6 @@ interface GroupCollectorInterface
* @return GroupCollectorInterface * @return GroupCollectorInterface
*/ */
public function externalIdDoesNotStart(string $externalId): GroupCollectorInterface; public function externalIdDoesNotStart(string $externalId): GroupCollectorInterface;
/** /**
* @param string $url * @param string $url
* @return GroupCollectorInterface * @return GroupCollectorInterface
@ -1038,9 +1032,6 @@ interface GroupCollectorInterface
* @return GroupCollectorInterface * @return GroupCollectorInterface
*/ */
public function excludeInternalReference(string $externalId): GroupCollectorInterface; public function excludeInternalReference(string $externalId): GroupCollectorInterface;
/** /**
* Limit the result to a set of specific transaction journals. * Limit the result to a set of specific transaction journals.
* *

View File

@ -151,10 +151,14 @@ class BudgetLimitController extends Controller
// sanity check on amount: // sanity check on amount:
if ((float) $amount === 0.0) { if ((float) $amount === 0.0) {
$amount = '1'; if (null !== $limit) {
$this->blRepository->destroyBudgetLimit($limit);
}
// return empty=ish array:
return response()->json([]);
} }
if ((int) $amount > 65536) { if ((int) $amount > 16777216) {
$amount = '65536'; $amount = '16777216';
} }
if (null !== $limit) { if (null !== $limit) {
@ -175,7 +179,7 @@ class BudgetLimitController extends Controller
if ($request->expectsJson()) { if ($request->expectsJson()) {
$array = $limit->toArray(); $array = $limit->toArray();
// add some extra meta data: // add some extra metadata:
$spentArr = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection([$budget]), $currency); $spentArr = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection([$budget]), $currency);
$array['spent'] = $spentArr[$currency->id]['sum'] ?? '0'; $array['spent'] = $spentArr[$currency->id]['sum'] ?? '0';
$array['left_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, bcadd($array['spent'], $array['amount'])); $array['left_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, bcadd($array['spent'], $array['amount']));
@ -208,10 +212,19 @@ class BudgetLimitController extends Controller
// sanity check on amount: // sanity check on amount:
if ((float) $amount === 0.0) { if ((float) $amount === 0.0) {
$amount = '1'; $budgetId = $budgetLimit->budget_id;
$currency = $budgetLimit->transactionCurrency;
$this->blRepository->destroyBudgetLimit($budgetLimit);
$array = [
'budget_id' => $budgetId,
'left_formatted' => app('amount')->formatAnything($currency, '0'),
'left_per_day_formatted' => app('amount')->formatAnything($currency, '0'),
'transaction_currency_id' => $currency->id,
];
return response()->json($array);
} }
if ((int) $amount > 65536) { if ((int) $amount > 16777216) { // 16 million
$amount = '65536'; $amount = '16777216';
} }
$limit = $this->blRepository->update($budgetLimit, ['amount' => $amount]); $limit = $this->blRepository->update($budgetLimit, ['amount' => $amount]);

View File

@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1);
/* /*
* InviteUserFormRequest.php * InviteUserFormRequest.php
* Copyright (c) 2022 james@firefly-iii.org * Copyright (c) 2022 james@firefly-iii.org

View File

@ -99,7 +99,7 @@ class RecurrenceFormRequest extends FormRequest
]; ];
// fill in foreign currency data // fill in foreign currency data
if (null !== $this->float('foreign_amount')) { if (null !== $this->convertFloat('foreign_amount')) {
$return['transactions'][0]['foreign_amount'] = $this->convertString('foreign_amount'); $return['transactions'][0]['foreign_amount'] = $this->convertString('foreign_amount');
$return['transactions'][0]['foreign_currency_id'] = $this->convertInteger('foreign_currency_id'); $return['transactions'][0]['foreign_currency_id'] = $this->convertInteger('foreign_currency_id');
} }
@ -228,7 +228,7 @@ class RecurrenceFormRequest extends FormRequest
$rules['repetitions'] = 'required|numeric|between:0,254'; $rules['repetitions'] = 'required|numeric|between:0,254';
} }
// if foreign amount, currency must be different. // if foreign amount, currency must be different.
if (null !== $this->float('foreign_amount')) { if (null !== $this->convertFloat('foreign_amount')) {
$rules['foreign_currency_id'] = 'exists:transaction_currencies,id|different:transaction_currency_id'; $rules['foreign_currency_id'] = 'exists:transaction_currencies,id|different:transaction_currency_id';
} }

View File

@ -0,0 +1,61 @@
<?php
/*
* InvitationMail.php
* Copyright (c) 2022 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class InvitationMail extends Mailable
{
use Queueable, SerializesModels;
public string $invitee;
public string $admin;
public string $url;
public string $host;
/**
* OAuthTokenCreatedMail constructor.
*
* @param string $ipAddress
*/
public function __construct(string $invitee, string $admin, string $url)
{
$this->invitee = $invitee;
$this->admin = $admin;
$this->url = $url;
$this->host = parse_url($url, PHP_URL_HOST);
}
/**
* Build the message.
*
* @return $this
*/
public function build(): self
{
return $this
->markdown('emails.invitation')
->subject((string) trans('email.invite_user_subject'));
}
}

View File

@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1);
/* /*
* InvitedUser.php * InvitedUser.php
* Copyright (c) 2022 james@firefly-iii.org * Copyright (c) 2022 james@firefly-iii.org

View File

@ -116,7 +116,7 @@ class EventServiceProvider extends ServiceProvider
], ],
InvitationCreated::class => [ InvitationCreated::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendInvitationNotification', 'FireflyIII\Handlers\Events\AdminEventHandler@sendInvitationNotification',
//'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationInvite', 'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationInvite',
], ],
// is a Transaction Journal related event. // is a Transaction Journal related event.

View File

@ -451,7 +451,7 @@ class UserRepository implements UserRepositoryInterface
public function validateInviteCode(string $code): bool public function validateInviteCode(string $code): bool
{ {
$now = Carbon::now(); $now = Carbon::now();
$invitee = InvitedUser::where('invite_code', $code)->where('expires', '<=', $now)->where('redeemed', 0)->first(); $invitee = InvitedUser::where('invite_code', $code)->where('expires', '>', $now->format('Y-m-d H:i:s'))->where('redeemed', 0)->first();
return null !== $invitee; return null !== $invitee;
} }

View File

@ -219,7 +219,7 @@ trait ConvertsDataTypes
* *
* @return float|null * @return float|null
*/ */
protected function float(string $field): ?float protected function convertFloat(string $field): ?float
{ {
$res = $this->get($field); $res = $this->get($field);
if (null === $res) { if (null === $res) {

View File

@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1);
/* /*
* AppendDescriptionToNotes.php * AppendDescriptionToNotes.php
* Copyright (c) 2022 james@firefly-iii.org * Copyright (c) 2022 james@firefly-iii.org

View File

@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1);
/* /*
* AppendNotesToDescription.php * AppendNotesToDescription.php
* Copyright (c) 2022 james@firefly-iii.org * Copyright (c) 2022 james@firefly-iii.org

View File

@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1);
/* /*
* MoveDescriptionToNotes.php * MoveDescriptionToNotes.php
* Copyright (c) 2022 james@firefly-iii.org * Copyright (c) 2022 james@firefly-iii.org

View File

@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1);
/* /*
* MoveNotesToDescription.php * MoveNotesToDescription.php
* Copyright (c) 2022 james@firefly-iii.org * Copyright (c) 2022 james@firefly-iii.org

View File

@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;

View File

@ -79,6 +79,7 @@ $(function () {
}); });
function updateBudgetedAmount(e) { function updateBudgetedAmount(e) {
console.log('updateBudgetedAmount');
var input = $(e.currentTarget); var input = $(e.currentTarget);
var budgetId = parseInt(input.data('id')); var budgetId = parseInt(input.data('id'));
var budgetLimitId = parseInt(input.data('limit')); var budgetLimitId = parseInt(input.data('limit'));
@ -95,7 +96,7 @@ function updateBudgetedAmount(e) {
}).done(function (data) { }).done(function (data) {
input.prop('disabled', false); input.prop('disabled', false);
input.data('limit', data.id);
// update amount left. // update amount left.
$('.left_span[data-limit="0"][data-id="' + budgetId + '"]').html(data.left_formatted); $('.left_span[data-limit="0"][data-id="' + budgetId + '"]').html(data.left_formatted);
if (data.left_per_day > 0) { if (data.left_per_day > 0) {
@ -113,6 +114,7 @@ function updateBudgetedAmount(e) {
amount: input.val(), amount: input.val(),
}).done(function (data) { }).done(function (data) {
input.prop('disabled', false); input.prop('disabled', false);
input.data('limit', data.id);
$('.left_span[data-limit="' + budgetLimitId + '"]').html(data.left_formatted); $('.left_span[data-limit="' + budgetLimitId + '"]').html(data.left_formatted);
if (data.left_per_day > 0) { if (data.left_per_day > 0) {
$('.left_span[data-limit="' + budgetLimitId + '"]').html(data.left_formatted + '(' + data.left_per_day_formatted + ')'); $('.left_span[data-limit="' + budgetLimitId + '"]').html(data.left_formatted + '(' + data.left_per_day_formatted + ')');

View File

@ -36,6 +36,10 @@ return [
// invite // invite
'invitation_created_subject' => 'An invitation has been created', 'invitation_created_subject' => 'An invitation has been created',
'invitation_created_body' => 'Admin user ":email" created a user invitation which can be used by whoever is behind email address ":invitee". The invite will be valid for 48hrs.', 'invitation_created_body' => 'Admin user ":email" created a user invitation which can be used by whoever is behind email address ":invitee". The invite will be valid for 48hrs.',
'invite_user_subject' => 'You\'ve been invited to create a Firefly III account.',
'invitation_introduction' => 'You\'ve been invited to create a Firefly III account on **:host**. Firefly III is a personal, self-hosted, private personal finance manager. All the cool kids are using it.',
'invitation_invited_by' => 'You\'ve been invited by ":admin" and this invitation was sent to ":invitee". That\'s you, right?',
'invitation_url' => 'The invitation is valid for 48 hours and can be redeemed by surfing to [Firefly III](:url). Enjoy!',
// new IP // new IP
'login_from_new_ip' => 'New login on Firefly III', 'login_from_new_ip' => 'New login on Firefly III',

View File

@ -1336,8 +1336,14 @@ return [
'slack_url_label' => 'Slack "incoming webhook" URL', 'slack_url_label' => 'Slack "incoming webhook" URL',
// profile: // profile:
'delete_stuff_header' => 'Delete data', 'purge_data_title' => 'Purge data from Firefly III',
'permanent_delete_stuff' => 'Be careful with these buttons. Deleting stuff is permanent.', 'purge_data_expl' => '"Purging" means "deleting that which is already deleted". In normal circumstances, Firefly III deletes nothing permanently. It just hides it. This can be annoying when you import data from other sources, as removed transactions will still be recognized as possible duplicates. The button below deletes all of these previously "deleted" records FOREVER.',
'delete_stuff_header' => 'Delete and purge data',
'purge_all_data' => 'Purge all deleted records',
'purge_data' => 'Purge data',
'purged_all_records' => 'All deleted records have been purged.',
'delete_data_title' => 'Delete data from Firefly III',
'permanent_delete_stuff' => 'You can delete stuff from Firefly III. Using the buttons below means that your items will be removed from view and hidden. There is no undo-button for this, but the items may remain in the database where you can salvage them if necessary.',
'other_sessions_logged_out' => 'All your other sessions have been logged out.', 'other_sessions_logged_out' => 'All your other sessions have been logged out.',
'delete_all_budgets' => 'Delete ALL your budgets', 'delete_all_budgets' => 'Delete ALL your budgets',
'delete_all_categories' => 'Delete ALL your categories', 'delete_all_categories' => 'Delete ALL your categories',
@ -2283,6 +2289,7 @@ return [
'admin_notification_check_new_version' => 'A new version is available', 'admin_notification_check_new_version' => 'A new version is available',
'admin_notification_check_invite_created' => 'A user is invited to Firefly III', 'admin_notification_check_invite_created' => 'A user is invited to Firefly III',
'admin_notification_check_invite_redeemed' => 'A user invitation is redeemed', 'admin_notification_check_invite_redeemed' => 'A user invitation is redeemed',
'all_invited_users' => 'All invited users',
'save_notification_settings' => 'Save settings', 'save_notification_settings' => 'Save settings',
'notification_settings_saved' => 'The notification settings have been saved', 'notification_settings_saved' => 'The notification settings have been saved',

View File

@ -0,0 +1,8 @@
@component('mail::message')
{{ trans('email.invitation_introduction', ['host' => $host]) }}
{{ trans('email.invitation_invited_by', ['invitee' => $invitee, 'admin' => $admin]) }}
{{ trans('email.invitation_url', ['url' => $url]) }}
@endcomponent

View File

@ -13,7 +13,8 @@
<!-- Nav tabs --> <!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist"> <ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"> <li role="presentation" class="active">
<a href="{{ route('profile.index') }}#options" aria-controls="home" role="tab" data-toggle="tab">{{ 'options'|_ }}</a> <a href="{{ route('profile.index') }}#options" aria-controls="home" role="tab"
data-toggle="tab">{{ 'options'|_ }}</a>
</li> </li>
<li role="presentation"> <li role="presentation">
<a href="#cmd" aria-controls="profile" role="tab" data-toggle="tab">{{ 'command_line_token'|_ }}</a> <a href="#cmd" aria-controls="profile" role="tab" data-toggle="tab">{{ 'command_line_token'|_ }}</a>
@ -22,14 +23,16 @@
<a href="#oauth" aria-controls="messages" role="tab" data-toggle="tab">{{ 'oauth'|_ }}</a> <a href="#oauth" aria-controls="messages" role="tab" data-toggle="tab">{{ 'oauth'|_ }}</a>
</li> </li>
{% if true == isInternalAuth and true == isInternalIdentity %} {% if true == isInternalAuth and true == isInternalIdentity %}
<li role="presentation"> <li role="presentation">
<a href="#mfa" aria-controls="settings" role="tab" data-toggle="tab">{{ 'pref_two_factor_auth'|_ }}</a> <a href="#mfa" aria-controls="settings" role="tab"
</li> data-toggle="tab">{{ 'pref_two_factor_auth'|_ }}</a>
</li>
{% endif %} {% endif %}
{% if true == isInternalAuth and true == isInternalIdentity %} {% if true == isInternalAuth and true == isInternalIdentity %}
<li role="presentation"> <li role="presentation">
<a href="#delete" aria-controls="settings" role="tab" data-toggle="tab">{{ 'delete_stuff_header'|_ }}</a> <a href="#delete" aria-controls="settings" role="tab"
</li> data-toggle="tab">{{ 'delete_stuff_header'|_ }}</a>
</li>
{% endif %} {% endif %}
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
@ -45,23 +48,24 @@
<div class="col-lg-6"> <div class="col-lg-6">
<ul> <ul>
{% if true == isInternalAuth and true == isInternalIdentity %} {% if true == isInternalAuth and true == isInternalIdentity %}
<li> <li>
<a href="{{ route('profile.change-email') }}">{{ 'change_your_email'|_ }}</a> <a href="{{ route('profile.change-email') }}">{{ 'change_your_email'|_ }}</a>
</li> </li>
<li> <li>
<a href="{{ route('profile.change-password') }}">{{ 'change_your_password'|_ }}</a> <a href="{{ route('profile.change-password') }}">{{ 'change_your_password'|_ }}</a>
</li> </li>
{% endif %} {% endif %}
<li><a href="{{ route('logout') }}" class="logout-link">{{ 'logout'|_ }}</a></li> <li><a href="{{ route('logout') }}" class="logout-link">{{ 'logout'|_ }}</a>
</li>
{% if true == isInternalAuth and true == isInternalIdentity %} {% if true == isInternalAuth and true == isInternalIdentity %}
<li> <li>
<a href="{{ route('profile.logout-others') }}">{{ 'logout_other_sessions'|_ }}</a> <a href="{{ route('profile.logout-others') }}">{{ 'logout_other_sessions'|_ }}</a>
</li> </li>
<li><a class="text-danger" <li><a class="text-danger"
href="{{ route('profile.delete-account') }}">{{ 'delete_account'|_ }}</a> href="{{ route('profile.delete-account') }}">{{ 'delete_account'|_ }}</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
@ -102,153 +106,181 @@
</div> </div>
{% if true == isInternalAuth and true == isInternalIdentity %} {% if true == isInternalAuth and true == isInternalIdentity %}
<!-- MFA --> <!-- MFA -->
<div role="tabpanel" class="tab-pane" id="mfa"> <div role="tabpanel" class="tab-pane" id="mfa">
<div class="box box-default"> <div class="box box-default">
<div class="box-body"> <div class="box-body">
<p class="text-info">{{ 'pref_two_factor_auth_help'|_ }}</p> <p class="text-info">{{ 'pref_two_factor_auth_help'|_ }}</p>
{% if enabled2FA == true %} {% if enabled2FA == true %}
<p class="text-info"> <p class="text-info">
{{ trans_choice('firefly.pref_two_factor_backup_code_count', mfaBackupCount) }} {{ trans_choice('firefly.pref_two_factor_backup_code_count', mfaBackupCount) }}
</p> </p>
<div class="btn-group"> <div class="btn-group">
<a class="btn btn-info" href="{{ route('profile.code') }}"> <a class="btn btn-info" href="{{ route('profile.code') }}">
<span class="fa fa-recycle"></span> <span class="fa fa-recycle"></span>
{{ 'pref_two_factor_auth_reset_code'|_ }}</a> {{ 'pref_two_factor_auth_reset_code'|_ }}</a>
</div> </div>
<form method="post" action="{{ route('profile.delete-code') }}"> <form method="post" action="{{ route('profile.delete-code') }}">
<input type="hidden" name="_token" value="{{ csrf_token() }}" /> <input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<input class="btn btn-danger" style="margin-top:20px;" type="submit" name="submit" value="{{ 'pref_two_factor_auth_disable_2fa'|_ }}" /> <input class="btn btn-danger" style="margin-top:20px;" type="submit"
</form> name="submit" value="{{ 'pref_two_factor_auth_disable_2fa'|_ }}"/>
<form method="post" action="{{ route('profile.new-backup-codes') }}"> </form>
<input type="hidden" name="_token" value="{{ csrf_token() }}" /> <form method="post" action="{{ route('profile.new-backup-codes') }}">
<input class="btn btn-default" style="margin-top:20px;" type="submit" name="submit" value="{{ 'pref_two_factor_new_backup_codes'|_ }}" /> <input type="hidden" name="_token" value="{{ csrf_token() }}"/>
</form> <input class="btn btn-default" style="margin-top:20px;" type="submit"
{% else %} name="submit" value="{{ 'pref_two_factor_new_backup_codes'|_ }}"/>
<form action="{{ route('profile.enable2FA') }}" method="post"> </form>
<input type="hidden" name="_token" value="{{ csrf_token() }}"/> {% else %}
<button type="submit" class="btn btn-info"><span <form action="{{ route('profile.enable2FA') }}" method="post">
class="fa fa-lock"></span> {{ 'pref_enable_two_factor_auth'|_ }}</button> <input type="hidden" name="_token" value="{{ csrf_token() }}"/>
</form> <button type="submit" class="btn btn-info"><span
{% endif %} class="fa fa-lock"></span> {{ 'pref_enable_two_factor_auth'|_ }}
</button>
</form>
{% endif %}
</div>
</div> </div>
</div> </div>
</div>
{% endif %} {% endif %}
<!-- delete stuff --> <!-- purge stuff -->
<div role="tabpanel" class="tab-pane" id="delete"> <div role="tabpanel" class="tab-pane" id="delete">
<div class="box box-default"> <div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">{{ 'purge_data_title'|_ }}</h3>
</div>
<div class="box-body with-border"> <div class="box-body with-border">
<div class="col-lg-12"> <div class="col-lg-12">
<p class="text-info">
{{ 'purge_data_expl'|_ }}
</p>
<p> <p>
<button type="button"
data-success="{{ trans('firefly.purged_all_records')|escape('html') }}"
data-type="purge" class="confirm btn btn-danger btn-sm"><span
class="fa fa-trash"></span> {{ 'purge_all_data'|_ }}</button>
</p>
</div>
</div>
</div>
<!-- delete stuff -->
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">{{ 'delete_data_title'|_ }}</h3>
</div>
<div class="box-body with-border">
<div class="col-lg-12">
<p class="text-info">
{{ 'permanent_delete_stuff'|_ }} {{ 'permanent_delete_stuff'|_ }}
</p> </p>
<h4>{{ 'financial_control'|_ }}</h4> <h4>{{ 'financial_control'|_ }}</h4>
<p> <div class="btn-group">
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_budgets')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_budgets')|escape('html') }}"
data-type="budgets" class="confirm btn btn-danger btn-sm"><span data-type="budgets" class="confirm btn btn-warning btn-sm"><span
class="fa fa-pie-chart"></span> {{ 'delete_all_budgets'|_ }}</button> class="fa fa-pie-chart"></span> {{ 'delete_all_budgets'|_ }}</button>
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_bills')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_bills')|escape('html') }}"
data-type="bills" class="confirm btn btn-danger btn-sm"><span data-type="bills" class="confirm btn btn-warning btn-sm"><span
class="fa fa-calendar-o"></span> {{ 'delete_all_bills'|_ }}</button> class="fa fa-calendar-o"></span> {{ 'delete_all_bills'|_ }}</button>
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_piggy_banks')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_piggy_banks')|escape('html') }}"
data-type="piggy_banks" class="confirm btn btn-danger btn-sm"><span data-type="piggy_banks" class="confirm btn btn-warning btn-sm"><span
class="fa fa-bullseye"></span> {{ 'delete_all_piggy_banks'|_ }}</button> class="fa fa-bullseye"></span> {{ 'delete_all_piggy_banks'|_ }}</button>
</p> </div>
<h4>{{ 'automation'|_ }}</h4> <h4>{{ 'automation'|_ }}</h4>
<p> <div class="btn-group">
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_rules')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_rules')|escape('html') }}"
data-type="rules" class="confirm btn btn-danger btn-sm"><span data-type="rules" class="confirm btn btn-warning btn-sm"><span
class="fa fa-random"></span> {{ 'delete_all_rules'|_ }}</button> class="fa fa-random"></span> {{ 'delete_all_rules'|_ }}</button>
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_recurring')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_recurring')|escape('html') }}"
data-type="recurring" class="confirm btn btn-danger btn-sm"><span data-type="recurring" class="confirm btn btn-warning btn-sm"><span
class="fa fa-paint-brush"></span> {{ 'delete_all_recurring'|_ }}</button> class="fa fa-paint-brush"></span> {{ 'delete_all_recurring'|_ }}
</p> </button>
</div>
<h4>{{ 'classification'|_ }}</h4> <h4>{{ 'classification'|_ }}</h4>
<p> <div class="btn-group">
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_categories')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_categories')|escape('html') }}"
data-type="categories" class="confirm btn btn-danger btn-sm"><span data-type="categories" class="confirm btn btn-warning btn-sm"><span
class="fa fa-bookmark"></span> {{ 'delete_all_categories'|_ }}</button> class="fa fa-bookmark"></span> {{ 'delete_all_categories'|_ }}</button>
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_tags')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_tags')|escape('html') }}"
data-type="tags" class="confirm btn btn-danger btn-sm"><span data-type="tags" class="confirm btn btn-warning btn-sm"><span
class="fa fa-tag"></span> {{ 'delete_all_tags'|_ }}</button> class="fa fa-tag"></span> {{ 'delete_all_tags'|_ }}</button>
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_object_groups')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_object_groups')|escape('html') }}"
data-type="object_groups" class="confirm btn btn-danger btn-sm"><span data-type="object_groups" class="confirm btn btn-warning btn-sm"><span
class="fa fa-envelope-o"></span> {{ 'delete_all_object_groups'|_ }}</button> class="fa fa-envelope-o"></span> {{ 'delete_all_object_groups'|_ }}
</p> </button>
</div>
<h4>{{ 'accounts'|_ }}</h4> <h4>{{ 'accounts'|_ }}</h4>
<p> <p>
<em class="text-danger">{{ 'also_delete_transactions'|_ }}</em> <em class="text-danger">{{ 'also_delete_transactions'|_ }}</em>
</p> </p>
<p> <div class="btn-group">
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_accounts')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_accounts')|escape('html') }}"
data-type="accounts" class="confirm btn btn-danger btn-sm"><span data-type="accounts" class="confirm btn btn-warning btn-sm"><span
class="fa fa-credit-card"></span> {{ 'delete_all_accounts'|_ }}</button> class="fa fa-credit-card"></span> {{ 'delete_all_accounts'|_ }}</button>
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_asset_accounts')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_asset_accounts')|escape('html') }}"
data-type="asset_accounts" class="confirm btn btn-danger btn-sm"><span data-type="asset_accounts" class="confirm btn btn-warning btn-sm"><span
class="fa fa-money"></span> {{ 'delete_all_asset_accounts'|_ }}</button> class="fa fa-money"></span> {{ 'delete_all_asset_accounts'|_ }}</button>
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_expense_accounts')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_expense_accounts')|escape('html') }}"
data-type="expense_accounts" class="confirm btn btn-danger btn-sm"><span data-type="expense_accounts" class="confirm btn btn-warning btn-sm"><span
class="fa fa-shopping-cart"></span> {{ 'delete_all_expense_accounts'|_ }}</button> class="fa fa-shopping-cart"></span> {{ 'delete_all_expense_accounts'|_ }}
</p> </button>
<p>
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_revenue_accounts')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_revenue_accounts')|escape('html') }}"
data-type="revenue_accounts" class="confirm btn btn-danger btn-sm"><span data-type="revenue_accounts" class="confirm btn btn-warning btn-sm"><span
class="fa fa-download"></span> {{ 'delete_all_revenue_accounts'|_ }}</button> class="fa fa-download"></span> {{ 'delete_all_revenue_accounts'|_ }}
</button>
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_liabilities')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_liabilities')|escape('html') }}"
data-type="liabilities" class="confirm btn btn-danger btn-sm"><span data-type="liabilities" class="confirm btn btn-warning btn-sm"><span
class="fa fa-ticket"></span> {{ 'delete_all_liabilities'|_ }}</button> class="fa fa-ticket"></span> {{ 'delete_all_liabilities'|_ }}</button>
</p> </div>
<h4>{{ 'transactions'|_ }}</h4> <h4>{{ 'transactions'|_ }}</h4>
<p> <div class="btn-group">
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_transactions')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_transactions')|escape('html') }}"
data-type="transactions" class="confirm btn btn-danger btn-sm"><span data-type="transactions" class="confirm btn btn-warning btn-sm"><span
class="fa fa-exchange"></span> {{ 'delete_all_transactions'|_ }}</button> class="fa fa-exchange"></span> {{ 'delete_all_transactions'|_ }}
</button>
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_withdrawals')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_withdrawals')|escape('html') }}"
data-type="withdrawals" class="confirm btn btn-danger btn-sm"><span data-type="withdrawals" class="confirm btn btn-warning btn-sm"><span
class="fa fa-long-arrow-left"></span> {{ 'delete_all_withdrawals'|_ }}</button> class="fa fa-long-arrow-left"></span> {{ 'delete_all_withdrawals'|_ }}
</button>
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_deposits')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_deposits')|escape('html') }}"
data-type="deposits" class="confirm btn btn-danger btn-sm"><span data-type="deposits" class="confirm btn btn-warning btn-sm"><span
class="fa fa-long-arrow-right"></span> {{ 'delete_all_deposits'|_ }}</button> class="fa fa-long-arrow-right"></span> {{ 'delete_all_deposits'|_ }}
</button>
</p>
<p>
<button type="button" <button type="button"
data-success="{{ trans('firefly.deleted_all_transfers')|escape('html') }}" data-success="{{ trans('firefly.deleted_all_transfers')|escape('html') }}"
data-type="transfers" class="confirm btn btn-danger btn-sm"><span data-type="transfers" class="confirm btn btn-warning btn-sm"><span
class="fa fa-exchange"></span> {{ 'delete_all_transfers'|_ }}</button> class="fa fa-exchange"></span> {{ 'delete_all_transfers'|_ }}</button>
</p> </div>
</div> </div>
</div> </div>
</div> </div>
@ -267,6 +299,11 @@
$('.confirm').on('click', function (e) { $('.confirm').on('click', function (e) {
var link = $(e.currentTarget); var link = $(e.currentTarget);
var classes = link.find('i').attr('class'); var classes = link.find('i').attr('class');
var url = deleteAPIRoute + '?objects=' + link.data('type');
// different URL for purge route:
if(link.data('type') === 'purge') {
url = '{{ route('api.v1.data.purge') }}';
}
// replace icon with loading thing // replace icon with loading thing
link.prop('disabled', true); link.prop('disabled', true);
@ -275,7 +312,7 @@
// call API: // call API:
$.ajax({ $.ajax({
method: 'DELETE', method: 'DELETE',
url: deleteAPIRoute + '?objects=' + link.data('type'), url: url,
}).done( }).done(
function () { function () {
// enable button again: // enable button again:

View File

@ -181,6 +181,13 @@ Route::group(
Route::delete('', ['uses' => 'DestroyController@destroy', 'as' => 'destroy']); Route::delete('', ['uses' => 'DestroyController@destroy', 'as' => 'destroy']);
} }
); );
Route::group(
['namespace' => 'FireflyIII\Api\V1\Controllers\Data', 'prefix' => 'v1/data/purge',
'as' => 'api.v1.data.',],
static function () {
Route::delete('', ['uses' => 'PurgeController@purge', 'as' => 'purge']);
}
);
// Bulk update API routes // Bulk update API routes
Route::group( Route::group(