Compare commits

...

10 Commits

Author SHA1 Message Date
Dan Brown
c10d2a1493 Updated assets for release v0.12.2 2016-10-30 13:19:19 +00:00
Dan Brown
97bbf79ffd Merge branch 'v0.12' into release 2016-10-30 13:18:23 +00:00
Dan Brown
2af0021c2b Fixed image tests after amends to url system 2016-10-30 12:58:01 +00:00
Dan Brown
0f2eaccb39 Added quick test to cover hypen breakage 2016-10-30 12:15:11 +00:00
Dan Brown
b251671e3f Amended search to not break on non-alpha-num chars
And also fixed exact term matches that contain non-alpha-num chars
Fixes #212
2016-10-30 12:08:22 +00:00
Dan Brown
c4eed37d8e Added custom head content into public pages
Closes #211
2016-10-30 11:46:23 +00:00
Dan Brown
8b43b91057 Improved password reset flow with notifications.
Also added links to sign-in/register.
Fixed links in emails sent out.

Fixes #210 and #218.
2016-10-30 11:36:57 +00:00
Dan Brown
91fe7f0bee Fixed PDF export table width
Closes #203

Signed-off-by: Dan Brown <ssddanbrown@googlemail.com>
2016-10-30 10:41:18 +00:00
Dan Brown
5cfb7b8de4 Altered 'ol' element padding to not clip numbering
Allows usage to 3-digits now
Closes #204
2016-10-30 10:23:49 +00:00
Dan Brown
6329a1842a Fixed issue with callouts overflowing page tags
Closes #179
2016-10-30 10:19:45 +00:00
20 changed files with 161 additions and 50 deletions

View File

@@ -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('/&quot;.*?&quot;/', $term)) {
$term = str_replace('&quot;', '', $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('/&quot;.*?&quot;/', $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');

View File

@@ -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));
}
}

View File

@@ -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;
}

View File

@@ -8,6 +8,8 @@ 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)',
'app-custom-head' => false,
'registration-enabled' => false,
];

View File

@@ -1,5 +1,5 @@
{
"css/styles.css": "css/styles.css?version=10caf1a",
"css/print-styles.css": "css/print-styles.css?version=10caf1a",
"js/common.js": "js/common.js?version=10caf1a"
"css/styles.css": "css/styles.css?version=5be13a8",
"css/print-styles.css": "css/print-styles.css?version=5be13a8",
"js/common.js": "js/common.js?version=5be13a8"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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;

View File

@@ -252,7 +252,7 @@ ul {
ol {
list-style: decimal;
padding-left: $-m * 1.3;
padding-left: $-m * 2;
overflow: hidden;
}

View File

@@ -1,5 +1,12 @@
@extends('public')
@section('header-buttons')
<a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>Sign in</a>
@if(setting('registration-enabled'))
<a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>Sign up</a>
@endif
@stop
@section('content')

View File

@@ -1,5 +1,12 @@
@extends('public')
@section('header-buttons')
<a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>Sign in</a>
@if(setting('registration-enabled'))
<a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>Sign up</a>
@endif
@stop
@section('body-class', 'image-cover login')
@section('content')

View File

@@ -23,7 +23,7 @@
@include('partials/custom-styles')
<!-- Custom user content -->
@if(setting('app-custom-head', false))
@if(setting('app-custom-head'))
{!! setting('app-custom-head') !!}
@endif
</head>

View File

@@ -162,14 +162,14 @@
<h1 style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;color:#444;margin-top:10px;margin-bottom:10px;margin-right:0;margin-left:0;line-height:1.2;font-weight:200;font-size:36px;">
Email Confirmation</h1>
<p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;line-height:1.6;margin-bottom:10px;font-weight:normal;font-size:14px;color:#888888;">
Thanks for joining <a href="{{ baseUrl('/') }}">{{ setting('app-name')}}</a>. <br/>
Thanks for joining <a href="{{ baseUrl('/', true) }}">{{ setting('app-name')}}</a>. <br/>
Please confirm your email address by clicking the button below.</p>
<table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;">
<tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
<td class="padding"
style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;padding-top:10px;padding-bottom:10px;padding-right:0;padding-left:0;">
<p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;line-height:1.6;margin-bottom:10px;font-weight:normal;font-size:14px;color:#888888;">
<a class="btn-primary" href="{{ baseUrl('/register/confirm/' . $token) }}"
<a class="btn-primary" href="{{ baseUrl('/register/confirm/' . $token, true) }}"
style="margin-top:0;margin-bottom:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;text-decoration:none;color:#FFF;background-color:#348eda;border-style:solid;border-color:#348eda;border-width:10px 20px;line-height:2;font-weight:bold;margin-right:10px;text-align:center;cursor:pointer;display:inline-block;border-radius:4px;">Confirm
Email</a></p>
</td>

File diff suppressed because one or more lines are too long

View File

@@ -14,7 +14,7 @@
table {
max-width: 800px !important;
font-size: 0.8em;
width: auto !important;
width: 100% !important;
}
table td {

View File

@@ -17,6 +17,11 @@
<!-- Scripts -->
<script src="{{ baseUrl("/libs/jquery/jquery.min.js?version=2.1.4") }}"></script>
@include('partials/custom-styles')
<!-- Custom user content -->
@if(setting('app-custom-head'))
{!! setting('app-custom-head') !!}
@endif
</head>
<body class="@yield('body-class')" ng-app="bookStack">

View File

@@ -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

View File

@@ -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();

View File

@@ -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()