This commit is contained in:
James Cole
2019-08-01 06:22:07 +02:00
parent b049ca27f1
commit 81dce5d7c7
10 changed files with 384 additions and 33 deletions

View File

@@ -0,0 +1,168 @@
<?php
/**
* GracefulNotFoundHandler.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Exceptions;
use Exception;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\User;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Request;
use Log;
/**
* Class GracefulNotFoundHandler
*/
class GracefulNotFoundHandler extends ExceptionHandler
{
/**
* Render an exception into an HTTP response.
*
* @param Request $request
* @param Exception $exception
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*
* @return mixed
*/
public function render($request, Exception $exception)
{
$route = $request->route();
$name = $route->getName();
if (!auth()->check()) {
return parent::render($request, $exception);
}
switch ($name) {
default:
Log::error(sprintf('GracefulNotFoundHandler cannot handle route with name "%s"', $name));
return parent::render($request, $exception);
case 'accounts.show':
return $this->handleAccount($request, $exception);
case 'transactions.show':
return $this->handleGroup($request, $exception);
break;
case 'attachments.show':
return redirect(route('index'));
break;
case 'bills.show':
$request->session()->reflash();
return redirect(route('bills.index'));
break;
case 'currencies.show':
$request->session()->reflash();
return redirect(route('currencies.index'));
break;
case 'budgets.show':
$request->session()->reflash();
return redirect(route('budgets.index'));
break;
case 'categories.show':
$request->session()->reflash();
return redirect(route('categories.index'));
break;
case 'rules.edit':
$request->session()->reflash();
return redirect(route('rules.index'));
break;
case 'transactions.mass.edit':
case 'transactions.mass.delete':
case 'transactions.bulk.edit':
$request->session()->reflash();
return redirect(route('index'));
break;
}
}
/**
* @param Request $request
* @param Exception $exception
*
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response
*/
private function handleAccount($request, Exception $exception)
{
Log::debug('404 page is probably a deleted account. Redirect to overview of account types.');
/** @var User $user */
$user = auth()->user();
$route = $request->route();
$accountId = (int)$route->parameter('account');
/** @var Account $account */
$account = $user->accounts()->with(['accountType'])->withTrashed()->find($accountId);
if (null === $account) {
Log::error(sprintf('Could not find account %d, so give big fat error.', $accountId));
return parent::render($request, $exception);
}
$type = $account->accountType;
$shortType = config(sprintf('firefly.shortNamesByFullName.%s', $type->type));
$request->session()->reflash();
return redirect(route('accounts.index', [$shortType]));
}
/**
* @param $request
* @param Exception $exception
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Routing\Redirector|\Symfony\Component\HttpFoundation\Response
*/
private function handleGroup($request, Exception $exception)
{
Log::debug('404 page is probably a deleted group. Redirect to overview of group types.');
/** @var User $user */
$user = auth()->user();
$route = $request->route();
$groupId = (int)$route->parameter('transactionGroup');
/** @var TransactionGroup $group */
$group = $user->transactionGroups()->withTrashed()->find($groupId);
if (null === $group) {
Log::error(sprintf('Could not find group %d, so give big fat error.', $groupId));
return parent::render($request, $exception);
}
/** @var TransactionJournal $journal */
$journal = $group->transactionJournals()->withTrashed()->first();
if (null === $journal) {
Log::error(sprintf('Could not find journal for group %d, so give big fat error.', $groupId));
return parent::render($request, $exception);
}
$type = $journal->transactionType->type;
$request->session()->reflash();
return redirect(route('transactions.index', [strtolower($type)]));
}
}

View File

@@ -92,6 +92,12 @@ class Handler extends ExceptionHandler
return response()->json(['message' => 'Internal Firefly III Exception. See log files.', 'exception' => get_class($exception)], 500); return response()->json(['message' => 'Internal Firefly III Exception. See log files.', 'exception' => get_class($exception)], 500);
} }
if($exception instanceof NotFoundHttpException) {
$handler = app(GracefulNotFoundHandler::class);
return $handler->render($request, $exception);
}
if ($exception instanceof FireflyException || $exception instanceof ErrorException || $exception instanceof OAuthServerException) { if ($exception instanceof FireflyException || $exception instanceof ErrorException || $exception instanceof OAuthServerException) {
$isDebug = config('app.debug'); $isDebug = config('app.debug');

View File

@@ -0,0 +1,112 @@
<?php
/**
* DeleteController.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Http\Controllers\Transaction;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use Illuminate\Http\RedirectResponse;
use Log;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use URL;
/**
* Class DeleteController
*/
class DeleteController extends Controller
{
/** @var TransactionGroupRepositoryInterface */
private $repository;
/**
* IndexController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{
parent::__construct();
// translations:
$this->middleware(
function ($request, $next) {
app('view')->share('title', (string)trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-repeat');
$this->repository = app(TransactionGroupRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Shows the form that allows a user to delete a transaction journal.
*
* @param TransactionGroup $group
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/
public function delete(TransactionGroup $group)
{
Log::debug(sprintf('Start of delete view for group #%d', $group->id));
$journal = $group->transactionJournals->first();
if (null === $journal) {
throw new NotFoundHttpException;
}
$objectType = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
$subTitle = (string)trans('firefly.delete_' . $objectType, ['description' => $group->title ?? $journal->description]);
$previous = URL::previous(route('index'));
// put previous url in session
Log::debug('Will try to remember previous URI');
$this->rememberPreviousUri('transactions.delete.uri');
return view('transactions.delete', compact('group', 'journal', 'subTitle', 'objectType', 'previous'));
}
/**
* Actually destroys the journal.
*
* @param TransactionGroup $group
*
* @return RedirectResponse
*/
public function destroy(TransactionGroup $group): RedirectResponse
{
$journal = $group->transactionJournals->first();
if (null === $journal) {
throw new NotFoundHttpException;
}
$objectType = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
session()->flash('success', (string)trans('firefly.deleted_' . strtolower($objectType), ['description' => $group->title ?? $journal->description]));
$this->repository->destroy($group);
app('preferences')->mark();
return redirect($this->getPreviousUri('transactions.delete.uri'));
}
}

View File

@@ -39,6 +39,7 @@ use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalLink; use FireflyIII\Models\TransactionJournalLink;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Services\Internal\Destroy\TransactionGroupDestroyService;
use FireflyIII\Services\Internal\Update\GroupUpdateService; use FireflyIII\Services\Internal\Update\GroupUpdateService;
use FireflyIII\Support\NullArrayObject; use FireflyIII\Support\NullArrayObject;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
@@ -46,7 +47,7 @@ use Illuminate\Database\Eloquent\Builder;
/** /**
* Class TransactionGroupRepository * Class TransactionGroupRepository
*/ */
class TransactionGroupRepository implements TransactionGroupRepositoryInterface class TransactionGroupRepository implements TransactionGroupRepositoryInterface
{ {
private $user; private $user;
@@ -364,4 +365,14 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
return $return; return $return;
} }
/**
* @param TransactionGroup $group
*/
public function destroy(TransactionGroup $group): void
{
/** @var TransactionGroupDestroyService $service */
$service = new TransactionGroupDestroyService;
$service->destroy($group);
}
} }

View File

@@ -23,8 +23,8 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\TransactionGroup; namespace FireflyIII\Repositories\TransactionGroup;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionGroup;
use FireflyIII\Services\Internal\Destroy\TransactionGroupDestroyService;
use FireflyIII\Support\NullArrayObject; use FireflyIII\Support\NullArrayObject;
use FireflyIII\User; use FireflyIII\User;
@@ -43,6 +43,11 @@ interface TransactionGroupRepositoryInterface
*/ */
public function getAttachments(TransactionGroup $group): array; public function getAttachments(TransactionGroup $group): array;
/**
* @param TransactionGroup $group
*/
public function destroy(TransactionGroup $group): void;
/** /**
* Return all journal links for all journals in the group. * Return all journal links for all journals in the group.
* *

View File

@@ -50,42 +50,46 @@ trait UserNavigation
*/ */
protected function getPreviousUri(string $identifier): string protected function getPreviousUri(string $identifier): string
{ {
// Log::debug(sprintf('Trying to retrieve URL stored under "%s"', $identifier)); Log::debug(sprintf('Trying to retrieve URL stored under "%s"', $identifier));
// "forbidden" words for specific identifiers:
// if these are in the previous URI, don't refer back there.
$array = [
'accounts.delete.uri' => '/accounts/show/',
'transactions.delete.uri' => '/transactions/show/',
'attachments.delete.uri' => '/attachments/show/',
'bills.delete.uri' => '/bills/show/',
'budgets.delete.uri' => '/budgets/show/',
'categories.delete.uri' => '/categories/show/',
'currencies.delete.uri' => '/currencies/show/',
'piggy-banks.delete.uri' => '/piggy-banks/show/',
'tags.delete.uri' => '/tags/show/',
'rules.delete.uri' => '/rules/edit/',
'transactions.mass-delete.uri' => '/transactions/show/',
];
$forbidden = $array[$identifier] ?? '/show/';
//Log::debug(sprintf('The forbidden word for %s is "%s"', $identifier, $forbidden));
$uri = (string)session($identifier); $uri = (string)session($identifier);
//Log::debug(sprintf('The URI is %s', $uri)); Log::debug(sprintf('The URI is %s', $uri));
if (
!(false === strpos($identifier, 'delete'))
&& !(false === strpos($uri, $forbidden))) {
$uri = $this->redirectUri;
//Log::debug(sprintf('URI is now %s (identifier contains "delete")', $uri));
}
if (!(false === strpos($uri, 'jscript'))) { if (!(false === strpos($uri, 'jscript'))) {
$uri = $this->redirectUri; // @codeCoverageIgnore $uri = $this->redirectUri; // @codeCoverageIgnore
//Log::debug(sprintf('URI is now %s (uri contains jscript)', $uri)); Log::debug(sprintf('URI is now %s (uri contains jscript)', $uri));
} }
// "forbidden" words for specific identifiers:
// if these are in the previous URI, don't refer back there.
// $array = [
// 'accounts.delete.uri' => '/accounts/show/',
// 'transactions.delete.uri' => '/transactions/show/',
// 'attachments.delete.uri' => '/attachments/show/',
// 'bills.delete.uri' => '/bills/show/',
// 'budgets.delete.uri' => '/budgets/show/',
// 'categories.delete.uri' => '/categories/show/',
// 'currencies.delete.uri' => '/currencies/show/',
// 'piggy-banks.delete.uri' => '/piggy-banks/show/',
// 'tags.delete.uri' => '/tags/show/',
// 'rules.delete.uri' => '/rules/edit/',
// 'transactions.mass-delete.uri' => '/transactions/show/',
// ];
//$forbidden = $array[$identifier] ?? '/show/';
//Log::debug(sprintf('The forbidden word for %s is "%s"', $identifier, $forbidden));
// if (
// !(false === strpos($identifier, 'delete'))
// && !(false === strpos($uri, $forbidden))) {
// $uri = $this->redirectUri;
// //Log::debug(sprintf('URI is now %s (identifier contains "delete")', $uri));
// }
// more debug notes: // more debug notes:
//Log::debug(sprintf('strpos($identifier, "delete"): %s', var_export(strpos($identifier, 'delete'), true))); //Log::debug(sprintf('strpos($identifier, "delete"): %s', var_export(strpos($identifier, 'delete'), true)));
//Log::debug(sprintf('strpos($uri, $forbidden): %s', var_export(strpos($uri, $forbidden), true))); //Log::debug(sprintf('strpos($uri, $forbidden): %s', var_export(strpos($uri, $forbidden), true)));
Log::debug(sprintf('Return direct link %s', $uri));
return $uri; return $uri;
} }

View File

@@ -39,6 +39,11 @@
The page you have requested does not exist. Please check that you have not entered The page you have requested does not exist. Please check that you have not entered
the wrong URL. Did you make a typo perhaps? the wrong URL. Did you make a typo perhaps?
</p> </p>
<p>
If you were redirected to this page automatically, please accept my apologies.
There is a mention of this error in your log files and I would be grateful if you sent
me the error to me.
</p>
</div> </div>
</div> </div>
<div class="row"> <div class="row">

View File

@@ -0,0 +1,35 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.render(Route.getCurrentRoute.getName, group) }}
{% endblock %}
{% block content %}
<form method="POST" action="{{ route('transactions.destroy',group.id) }}" accept-charset="UTF-8" class="form-horizontal" id="destroy">
<input name="_token" type="hidden" value="{{ csrf_token() }}">
<div class="row">
<div class="col-lg-6 col-lg-offset-3 col-md-6 col-sm-12">
<div class="box box-danger">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('form.delete_journal', {'description': group.title|default(journal.description)}) }}</h3>
</div>
<div class="box-body">
<p class="text-danger">
{{ trans('form.permDeleteWarning') }}
</p>
<p>
{{ trans('form.journal_areYouSure', {'description': group.title|default(journal.description)}) }}
</p>
</div>
<div class="box-footer">
<input type="submit" name="submit" value="{{ trans('form.deletePermanently') }}" class="btn btn-danger pull-right"/>
<a href="{{ previous }}" class="btn-default btn">{{ trans('form.cancel') }}</a>
</div>
</div>
</div>
</div>
</form>
{% endblock %}

View File

@@ -71,6 +71,8 @@
<div class="btn-group btn-group-xs"> <div class="btn-group btn-group-xs">
<a href="{{ route('transactions.edit', [transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-pencil"></i> {{ 'edit'|_ }} <a href="{{ route('transactions.edit', [transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-pencil"></i> {{ 'edit'|_ }}
</a> </a>
<a href="{{ route('transactions.delete', [transactionGroup.id]) }}" class="btn btn-danger"><i class="fa fa-trash"></i> {{ 'delete'|_ }}
</a>
{% if groupArray.transactions[0].type != 'withdrawal' %} {% if groupArray.transactions[0].type != 'withdrawal' %}
<a href="{{ route('transactions.convert.index', ['withdrawal', transactionGroup.id]) }}" class="btn btn-default"><i <a href="{{ route('transactions.convert.index', ['withdrawal', transactionGroup.id]) }}" class="btn btn-default"><i

View File

@@ -1055,10 +1055,13 @@ try {
Breadcrumbs::register( Breadcrumbs::register(
'transactions.delete', 'transactions.delete',
function (BreadcrumbsGenerator $breadcrumbs, TransactionJournal $journal) { static function (BreadcrumbsGenerator $breadcrumbs, TransactionGroup $group) {
$breadcrumbs->parent('transactions.show', $journal); $breadcrumbs->parent('transactions.show', $group);
$journal = $group->transactionJournals->first();
$breadcrumbs->push( $breadcrumbs->push(
trans('breadcrumbs.delete_journal', ['description' => limitStringLength($journal->description)]), route('transactions.delete', [$journal->id]) trans('breadcrumbs.delete_group', ['description' => limitStringLength($group->title ?? $journal->description)]),
route('transactions.delete', [$group->id])
); );
} }
); );