class ForgotPasswordController extends Controller
{
- /**
- * Create a new controller instance.
- *
- * @return void
- */
public function __construct()
{
$this->middleware('guest');
/**
* Send a reset link to the given user.
- *
- * @param \Illuminate\Http\Request $request
- *
- * @return \Illuminate\Http\RedirectResponse
*/
public function sendResetLinkEmail(Request $request)
{
$message = trans('auth.reset_password_sent', ['email' => $request->get('email')]);
$this->showSuccessNotification($message);
- return back()->with('status', trans($response));
+ return redirect('/password/email')->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(
+ return redirect('/password/email')->withErrors(
['email' => trans($response)]
);
}
// redirect them back to where they came from with their error message.
return $response === Password::PASSWORD_RESET
? $this->sendResetResponse()
- : $this->sendResetFailedResponse($request, $response);
+ : $this->sendResetFailedResponse($request, $response, $request->get('token'));
}
/**
/**
* Get the response for a failed password reset.
*/
- protected function sendResetFailedResponse(Request $request, string $response): RedirectResponse
+ protected function sendResetFailedResponse(Request $request, string $response, string $token): RedirectResponse
{
// We show invalid users as invalid tokens as to not leak what
// users may exist in the system.
$response = Password::INVALID_TOKEN;
}
- return redirect()->back()
+ return redirect("/password/reset/{$token}")
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
return $this->socialRegisterCallback($socialDriver, $socialUser);
}
- return redirect()->back();
+ return redirect('/');
}
/**
namespace BookStack\Activity\Controllers;
-use BookStack\Activity\Models\Favouritable;
-use BookStack\App\Model;
-use BookStack\Entities\Models\Entity;
use BookStack\Entities\Queries\TopFavourites;
use BookStack\Entities\Tools\MixedEntityRequestHelper;
use BookStack\Http\Controller;
'name' => $entity->name,
]));
- return redirect()->back();
+ return redirect($entity->getUrl());
}
/**
'name' => $entity->name,
]));
- return redirect()->back();
+ return redirect($entity->getUrl());
}
}
$this->showSuccessNotification(trans('activities.watch_update_level_notification'));
- return redirect()->back();
+ return redirect($watchable->getUrl());
}
}
use BookStack\Entities\Tools\NextPreviousContentLocator;
use BookStack\Exceptions\MoveOperationException;
use BookStack\Exceptions\NotFoundException;
+use BookStack\Exceptions\NotifyException;
use BookStack\Exceptions\PermissionsException;
use BookStack\Http\Controller;
use BookStack\References\ReferenceFetcher;
/**
* Perform the move action for a chapter.
*
- * @throws NotFoundException
+ * @throws NotFoundException|NotifyException
*/
public function move(Request $request, string $bookSlug, string $chapterSlug)
{
}
try {
- $newBook = $this->chapterRepo->move($chapter, $entitySelection);
+ $this->chapterRepo->move($chapter, $entitySelection);
} catch (PermissionsException $exception) {
$this->showPermissionError();
} catch (MoveOperationException $exception) {
$this->showErrorNotification(trans('errors.selected_book_not_found'));
- return redirect()->back();
+ return redirect($chapter->getUrl('/move'));
}
return redirect($chapter->getUrl());
if (is_null($newParentBook)) {
$this->showErrorNotification(trans('errors.selected_book_not_found'));
- return redirect()->back();
+ return redirect($chapter->getUrl('/copy'));
}
$this->checkOwnablePermission('chapter-create', $newParentBook);
} catch (Exception $exception) {
$this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
- return redirect()->back();
+ return redirect($page->getUrl('/move'));
}
return redirect($page->getUrl());
if (is_null($newParent)) {
$this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
- return redirect()->back();
+ return redirect($page->getUrl('/copy'));
}
$this->checkOwnablePermission('page-create', $newParent);
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\JsonResponse;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
abstract class Controller extends BaseController
{
return ['image_extension', 'mimes:jpeg,png,gif,webp', 'max:' . (config('app.upload_limit') * 1000)];
}
+
+ /**
+ * Redirect to the URL provided in the request as a '_return' parameter.
+ * Will check that the parameter leads to a URL under the root path of the system.
+ */
+ protected function redirectToRequest(Request $request): RedirectResponse
+ {
+ $basePath = url('/');
+ $returnUrl = $request->input('_return') ?? $basePath;
+
+ if (!str_starts_with($returnUrl, $basePath)) {
+ return redirect($basePath);
+ }
+
+ return redirect($returnUrl);
+ }
}
} catch (PermissionsException $e) {
$this->showErrorNotification($e->getMessage());
- return redirect()->back();
+ return redirect("/settings/roles/delete/{$id}");
}
return redirect('/settings/roles');
namespace BookStack\Users\Controllers;
use BookStack\Http\Controller;
-use BookStack\Permissions\PermissionApplicator;
-use BookStack\Settings\UserNotificationPreferences;
-use BookStack\Settings\UserShortcutMap;
use BookStack\Users\UserRepo;
use Illuminate\Http\Request;
{
$valueViewTypes = ['books', 'bookshelves', 'bookshelf'];
if (!in_array($type, $valueViewTypes)) {
- return redirect()->back(500);
+ return $this->redirectToRequest($request);
}
$view = $request->get('view');
$key = $type . '_view_type';
setting()->putForCurrentUser($key, $view);
- return redirect()->back(302, [], "/");
+ return $this->redirectToRequest($request);
}
/**
{
$validSortTypes = ['books', 'bookshelves', 'shelf_books', 'users', 'roles', 'webhooks', 'tags', 'page_revisions'];
if (!in_array($type, $validSortTypes)) {
- return redirect()->back(500);
+ return $this->redirectToRequest($request);
}
$sort = substr($request->get('sort') ?: 'name', 0, 50);
setting()->putForCurrentUser($sortKey, $sort);
setting()->putForCurrentUser($orderKey, $order);
- return redirect()->back(302, [], "/");
+ return $this->redirectToRequest($request);
}
/**
* Toggle dark mode for the current user.
*/
- public function toggleDarkMode()
+ public function toggleDarkMode(Request $request)
{
$enabled = setting()->getForCurrentUser('dark-mode-enabled');
setting()->putForCurrentUser('dark-mode-enabled', $enabled ? 'false' : 'true');
- return redirect()->back();
+ return $this->redirectToRequest($request);
}
/**
<form action="{{ url('/preferences/toggle-dark-mode') }}" method="post">
{{ csrf_field() }}
{{ method_field('patch') }}
+ <input type="hidden" name="_return" value="{{ url()->current() }}">
@if(setting()->getForCurrentUser('dark-mode-enabled'))
<button class="{{ $classes ?? '' }}"><span>@icon('light-mode')</span><span>{{ trans('common.light_mode') }}</span></button>
@else
method="post"
@endif
>
+ <input type="hidden" name="_return" value="{{ url()->current() }}">
@if($useQuery ?? false)
@foreach(array_filter(request()->except(['sort', 'order'])) as $key => $value)
<div>
<form action="{{ url("/preferences/change-view/" . $type) }}" method="POST" class="inline">
- {!! csrf_field() !!}
- {!! method_field('PATCH') !!}
+ {{ csrf_field() }}
+ {{ method_field('patch') }}
+ <input type="hidden" name="_return" value="{{ url()->current() }}">
@if ($view === 'list')
<button type="submit" name="view" value="grid" class="icon-list-item text-link">
</button>
@else
<button type="submit" name="view" value="list" class="icon-list-item text-link">
- <span>@icon('list')</span>
+ <span class="icon">@icon('list')</span>
<span>{{ trans('common.list_view') }}</span>
</button>
@endif
$editor = $this->users->editor();
$book = $this->entities->book();
- $this->actingAs($editor)->get($book->getUrl());
- $resp = $this->put('/watching/update', [
+ $resp = $this->actingAs($editor)->put('/watching/update', [
'type' => $book->getMorphClass(),
'id' => $book->id,
'level' => 'comments'
]);
}
+ public function test_add_and_remove_redirect_to_entity_without_history()
+ {
+ $page = $this->entities->page();
+
+ $resp = $this->asEditor()->post('/favourites/add', [
+ 'type' => $page->getMorphClass(),
+ 'id' => $page->id,
+ ]);
+ $resp->assertRedirect($page->getUrl());
+
+ $resp = $this->asEditor()->post('/favourites/remove', [
+ 'type' => $page->getMorphClass(),
+ 'id' => $page->id,
+ ]);
+ $resp->assertRedirect($page->getUrl());
+ }
+
public function test_favourite_flow_with_own_permissions()
{
$book = $this->entities->book();
/** @var Role $publicRole */
$publicRole = Role::getSystemRole('public');
$resp = $this->asAdmin()->delete('/settings/roles/delete/' . $publicRole->id);
- $resp->assertRedirect('/');
+ $resp->assertRedirect('/settings/roles/delete/' . $publicRole->id);
$this->get('/settings/roles/delete/' . $publicRole->id);
$resp = $this->delete('/settings/roles/delete/' . $publicRole->id);
namespace Tests\User;
-use BookStack\Activity\Tools\UserEntityWatchOptions;
-use BookStack\Activity\WatchLevels;
use Tests\TestCase;
class UserPreferencesTest extends TestCase
'sort' => 'name',
'order' => 'asc',
]);
- $updateRequest->assertStatus(500);
+ $updateRequest->assertRedirect();
$this->assertNotEmpty('name', setting()->getForCurrentUser('bookshelves_sort'));
$this->assertNotEmpty('asc', setting()->getForCurrentUser('bookshelves_sort_order'));
->assertElementNotExists('.featured-image-container')
->assertElementExists('.content-wrap .entity-list-item');
- $req = $this->patch("/preferences/change-view/bookshelf", ['view' => 'grid']);
+ $req = $this->patch("/preferences/change-view/bookshelf", [
+ 'view' => 'grid',
+ '_return' => $shelf->getUrl(),
+ ]);
$req->assertRedirect($shelf->getUrl());
$resp = $this->actingAs($editor)->get($shelf->getUrl())