From 6329a1842a0acddc4b68c5d093209495e8cfe634 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 30 Oct 2016 10:19:45 +0000 Subject: [PATCH 1/8] Fixed issue with callouts overflowing page tags Closes #179 --- resources/assets/sass/_blocks.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/assets/sass/_blocks.scss b/resources/assets/sass/_blocks.scss index 3c7f7490b..727633f75 100644 --- a/resources/assets/sass/_blocks.scss +++ b/resources/assets/sass/_blocks.scss @@ -135,6 +135,7 @@ border-left: 3px solid #BBB; background-color: #EEE; padding: $-s; + display: flex; &:before { font-family: 'Material-Design-Iconic-Font'; padding-right: $-s; From 5cfb7b8de4f34712b90eec79be48e355be29ff2c Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 30 Oct 2016 10:23:49 +0000 Subject: [PATCH 2/8] Altered 'ol' element padding to not clip numbering Allows usage to 3-digits now Closes #204 --- resources/assets/sass/_text.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/assets/sass/_text.scss b/resources/assets/sass/_text.scss index cd81bb4e2..8bf09a626 100644 --- a/resources/assets/sass/_text.scss +++ b/resources/assets/sass/_text.scss @@ -252,7 +252,7 @@ ul { ol { list-style: decimal; - padding-left: $-m * 1.3; + padding-left: $-m * 2; overflow: hidden; } From 91fe7f0bee4282fbdf32d3c30a5ec26d8d9b2be9 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 30 Oct 2016 10:26:12 +0000 Subject: [PATCH 3/8] Fixed PDF export table width Closes #203 Signed-off-by: Dan Brown --- resources/views/pages/pdf.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/pages/pdf.blade.php b/resources/views/pages/pdf.blade.php index 0cbf4df02..5c9fd5eea 100644 --- a/resources/views/pages/pdf.blade.php +++ b/resources/views/pages/pdf.blade.php @@ -14,7 +14,7 @@ table { max-width: 800px !important; font-size: 0.8em; - width: auto !important; + width: 100% !important; } table td { From 8b43b91057ffbe1da17a4a86daa4603445e84ffd Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 30 Oct 2016 11:33:56 +0000 Subject: [PATCH 4/8] Improved password reset flow with notifications. Also added links to sign-in/register. Fixed links in emails sent out. Fixes #210 and #218. --- .../Controllers/Auth/PasswordController.php | 44 +++++++++++++++++++ app/helpers.php | 5 +++ config/setting-defaults.php | 3 +- resources/views/auth/password.blade.php | 7 +++ resources/views/auth/reset.blade.php | 7 +++ .../views/emails/email-confirmation.blade.php | 4 +- resources/views/emails/password.blade.php | 2 +- tests/Auth/AuthTest.php | 31 +++++++++++++ 8 files changed, 99 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php index 038e444d2..4dc6583ea 100644 --- a/app/Http/Controllers/Auth/PasswordController.php +++ b/app/Http/Controllers/Auth/PasswordController.php @@ -4,6 +4,8 @@ namespace BookStack\Http\Controllers\Auth; use BookStack\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ResetsPasswords; +use Illuminate\Http\Request; +use Password; class PasswordController extends Controller { @@ -29,4 +31,46 @@ class PasswordController extends Controller { $this->middleware('guest'); } + + + /** + * Send a reset link to the given user. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function sendResetLinkEmail(Request $request) + { + $this->validate($request, ['email' => 'required|email']); + + $broker = $this->getBroker(); + + $response = Password::broker($broker)->sendResetLink( + $request->only('email'), $this->resetEmailBuilder() + ); + + switch ($response) { + case Password::RESET_LINK_SENT: + $message = 'A password reset link has been sent to ' . $request->get('email') . '.'; + session()->flash('success', $message); + return $this->getSendResetLinkEmailSuccessResponse($response); + + case Password::INVALID_USER: + default: + return $this->getSendResetLinkEmailFailureResponse($response); + } + } + + /** + * Get the response for after a successful password reset. + * + * @param string $response + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function getResetSuccessResponse($response) + { + $message = 'Your password has been successfully reset.'; + session()->flash('success', $message); + return redirect($this->redirectPath())->with('status', trans($response)); + } } diff --git a/app/helpers.php b/app/helpers.php index b8abb1006..c12708877 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -84,6 +84,11 @@ function baseUrl($path, $forceAppDomain = false) $path = implode('/', array_splice($explodedPath, 3)); } + // Return normal url path if not specified in config + if (config('app.url') === '') { + return url($path); + } + return rtrim(config('app.url'), '/') . '/' . $path; } diff --git a/config/setting-defaults.php b/config/setting-defaults.php index 24a49c364..22c992aa4 100644 --- a/config/setting-defaults.php +++ b/config/setting-defaults.php @@ -8,6 +8,7 @@ return [ 'app-name' => 'BookStack', 'app-editor' => 'wysiwyg', 'app-color' => '#0288D1', - 'app-color-light' => 'rgba(21, 101, 192, 0.15)' + 'app-color-light' => 'rgba(21, 101, 192, 0.15)', + 'registration-enabled' => false, ]; \ No newline at end of file diff --git a/resources/views/auth/password.blade.php b/resources/views/auth/password.blade.php index d8536efa7..115785ab2 100644 --- a/resources/views/auth/password.blade.php +++ b/resources/views/auth/password.blade.php @@ -1,5 +1,12 @@ @extends('public') +@section('header-buttons') + Sign in + @if(setting('registration-enabled')) + Sign up + @endif +@stop + @section('content') diff --git a/resources/views/auth/reset.blade.php b/resources/views/auth/reset.blade.php index 9a9a65ff0..612b50ff8 100644 --- a/resources/views/auth/reset.blade.php +++ b/resources/views/auth/reset.blade.php @@ -1,5 +1,12 @@ @extends('public') +@section('header-buttons') + Sign in + @if(setting('registration-enabled')) + Sign up + @endif +@stop + @section('body-class', 'image-cover login') @section('content') diff --git a/resources/views/emails/email-confirmation.blade.php b/resources/views/emails/email-confirmation.blade.php index 6a0dc0378..0a211c369 100644 --- a/resources/views/emails/email-confirmation.blade.php +++ b/resources/views/emails/email-confirmation.blade.php @@ -162,14 +162,14 @@

Email Confirmation

- Thanks for joining {{ setting('app-name')}}.
+ Thanks for joining {{ setting('app-name')}}.
Please confirm your email address by clicking the button below.

diff --git a/resources/views/emails/password.blade.php b/resources/views/emails/password.blade.php index dfd8f3db5..a8f7911e0 100644 --- a/resources/views/emails/password.blade.php +++ b/resources/views/emails/password.blade.php @@ -1 +1 @@ -Password Reset From {{ setting('app-name')}}

- Confirm Email

Password Reset

A password reset was requested for this email address on {{ setting('app-name')}}. If you did not request a password change please ignore this email.

Click here to reset your password

\ No newline at end of file + Password Reset From {{ setting('app-name')}}

Password Reset

A password reset was requested for this email address on {{ setting('app-name')}}. If you did not request a password change please ignore this email.

Click here to reset your password

\ No newline at end of file diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index 306771ed5..99885d552 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -216,6 +216,37 @@ class AuthTest extends TestCase ->seePageIs('/login'); } + public function test_reset_password_flow() + { + $this->visit('/login')->click('Forgot Password?') + ->seePageIs('/password/email') + ->type('admin@admin.com', 'email') + ->press('Send Reset Link') + ->see('A password reset link has been sent to admin@admin.com'); + + $this->seeInDatabase('password_resets', [ + 'email' => 'admin@admin.com' + ]); + + $reset = DB::table('password_resets')->where('email', '=', 'admin@admin.com')->first(); + $this->visit('/password/reset/' . $reset->token) + ->see('Reset Password') + ->submitForm('Reset Password', [ + 'email' => 'admin@admin.com', + 'password' => 'randompass', + 'password_confirmation' => 'randompass' + ])->seePageIs('/') + ->see('Your password has been successfully reset'); + } + + public function test_reset_password_page_shows_sign_links() + { + $this->setSettings(['registration-enabled' => 'true']); + $this->visit('/password/email') + ->seeLink('Sign in') + ->seeLink('Sign up'); + } + /** * Perform a login * @param string $email From c4eed37d8e20dbf235d54d00f5676288cd5481f6 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 30 Oct 2016 11:44:31 +0000 Subject: [PATCH 5/8] Added custom head content into public pages Closes #211 --- config/setting-defaults.php | 1 + resources/views/base.blade.php | 2 +- resources/views/public.blade.php | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/config/setting-defaults.php b/config/setting-defaults.php index 22c992aa4..deafceb29 100644 --- a/config/setting-defaults.php +++ b/config/setting-defaults.php @@ -9,6 +9,7 @@ return [ 'app-editor' => 'wysiwyg', 'app-color' => '#0288D1', 'app-color-light' => 'rgba(21, 101, 192, 0.15)', + 'app-custom-head' => false, 'registration-enabled' => false, ]; \ No newline at end of file diff --git a/resources/views/base.blade.php b/resources/views/base.blade.php index be47abdca..961ead251 100644 --- a/resources/views/base.blade.php +++ b/resources/views/base.blade.php @@ -23,7 +23,7 @@ @include('partials/custom-styles') - @if(setting('app-custom-head', false)) + @if(setting('app-custom-head')) {!! setting('app-custom-head') !!} @endif diff --git a/resources/views/public.blade.php b/resources/views/public.blade.php index 2de4d968a..2bce41570 100644 --- a/resources/views/public.blade.php +++ b/resources/views/public.blade.php @@ -17,6 +17,11 @@ @include('partials/custom-styles') + + + @if(setting('app-custom-head')) + {!! setting('app-custom-head') !!} + @endif From b251671e3f2fdd2778506f0b36889c22b18ba1eb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 30 Oct 2016 12:07:11 +0000 Subject: [PATCH 6/8] Amended search to not break on non-alpha-num chars And also fixed exact term matches that contain non-alpha-num chars Fixes #212 --- app/Entity.php | 62 ++++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/app/Entity.php b/app/Entity.php index 496d20a33..2c447814f 100644 --- a/app/Entity.php +++ b/app/Entity.php @@ -160,44 +160,46 @@ class Entity extends Ownable public function fullTextSearchQuery($fieldsToSearch, $terms, $wheres = []) { $exactTerms = []; - if (count($terms) === 0) { - $search = $this; - $orderBy = 'updated_at'; - } else { - foreach ($terms as $key => $term) { - $term = htmlentities($term, ENT_QUOTES); - $term = preg_replace('/[+\-><\(\)~*\"@]+/', ' ', $term); - if (preg_match('/".*?"/', $term)) { - $term = str_replace('"', '', $term); - $exactTerms[] = '%' . $term . '%'; - $term = '"' . $term . '"'; - } else { - $term = '' . $term . '*'; - } - if ($term !== '*') $terms[$key] = $term; + $fuzzyTerms = []; + $search = static::newQuery(); + foreach ($terms as $key => $term) { + $safeTerm = htmlentities($term, ENT_QUOTES); + $safeTerm = preg_replace('/[+\-><\(\)~*\"@]+/', ' ', $safeTerm); + if (preg_match('/".*?"/', $safeTerm) || is_numeric($safeTerm)) { + $safeTerm = preg_replace('/^"(.*?)"$/', '$1', $term); + $exactTerms[] = '%' . $safeTerm . '%'; + } else { + $safeTerm = '' . $safeTerm . '*'; + if (trim($safeTerm) !== '*') $fuzzyTerms[] = $safeTerm; } - $termString = implode(' ', $terms); - $fields = implode(',', $fieldsToSearch); - $search = static::selectRaw('*, MATCH(name) AGAINST(? IN BOOLEAN MODE) AS title_relevance', [$termString]); - $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]); + } + $isFuzzy = count($exactTerms) === 0 || count($fuzzyTerms) > 0; - // Ensure at least one exact term matches if in search - if (count($exactTerms) > 0) { - $search = $search->where(function ($query) use ($exactTerms, $fieldsToSearch) { - foreach ($exactTerms as $exactTerm) { - foreach ($fieldsToSearch as $field) { - $query->orWhere($field, 'like', $exactTerm); - } + // Perform fulltext search if relevant terms exist. + if ($isFuzzy) { + $termString = implode(' ', $fuzzyTerms); + $fields = implode(',', $fieldsToSearch); + $search = $search->selectRaw('*, MATCH(name) AGAINST(? IN BOOLEAN MODE) AS title_relevance', [$termString]); + $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]); + } + + // Ensure at least one exact term matches if in search + if (count($exactTerms) > 0) { + $search = $search->where(function ($query) use ($exactTerms, $fieldsToSearch) { + foreach ($exactTerms as $exactTerm) { + foreach ($fieldsToSearch as $field) { + $query->orWhere($field, 'like', $exactTerm); } - }); - } - $orderBy = 'title_relevance'; - }; + } + }); + } + $orderBy = $isFuzzy ? 'title_relevance' : 'updated_at'; // Add additional where terms foreach ($wheres as $whereTerm) { $search->where($whereTerm[0], $whereTerm[1], $whereTerm[2]); } + // Load in relations if ($this->isA('page')) { $search = $search->with('book', 'chapter', 'createdBy', 'updatedBy'); From 0f2eaccb390af1a355a5e8ac3d1f2c87b83fed4e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 30 Oct 2016 12:15:11 +0000 Subject: [PATCH 7/8] Added quick test to cover hypen breakage --- tests/Entity/EntitySearchTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/Entity/EntitySearchTest.php b/tests/Entity/EntitySearchTest.php index 8adfd35a3..cfdabdb0a 100644 --- a/tests/Entity/EntitySearchTest.php +++ b/tests/Entity/EntitySearchTest.php @@ -91,6 +91,12 @@ class EntitySearchTest extends TestCase ->see('Book Search Results')->see('.entity-list', $book->name); } + public function test_searching_hypen_doesnt_break() + { + $this->visit('/search/all?term=cat+-') + ->seeStatusCode(200); + } + public function test_ajax_entity_search() { $page = \BookStack\Page::all()->last(); From 2af0021c2bda81d72bff13d4516b0de6d75a6c11 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 30 Oct 2016 12:58:01 +0000 Subject: [PATCH 8/8] Fixed image tests after amends to url system --- tests/ImageTest.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/ImageTest.php b/tests/ImageTest.php index 806a36acc..23373419f 100644 --- a/tests/ImageTest.php +++ b/tests/ImageTest.php @@ -57,10 +57,12 @@ class ImageTest extends TestCase $relPath = $this->uploadImage($imageName, $page->id); $this->assertResponseOk(); - $this->assertTrue(file_exists(public_path($relPath)), 'Uploaded image exists'); + $this->assertTrue(file_exists(public_path($relPath)), 'Uploaded image not found at path: '. public_path($relPath)); + + $this->deleteImage($relPath); $this->seeInDatabase('images', [ - 'url' => $relPath, + 'url' => url($relPath), 'type' => 'gallery', 'uploaded_to' => $page->id, 'path' => $relPath, @@ -68,8 +70,7 @@ class ImageTest extends TestCase 'updated_by' => $admin->id, 'name' => $imageName ]); - - $this->deleteImage($relPath); + } public function test_image_delete()