Compare commits

...

10 Commits

Author SHA1 Message Date
Dan Brown
757cdddc7c Updated version and JS for release v0.18.5 2017-11-11 18:33:04 +00:00
Dan Brown
df95e99680 Updated assets and version for release v0.18.4 2017-10-15 19:28:29 +01:00
Dan Brown
5a6d544db7 Merge branch 'master' into release 2017-10-15 19:27:50 +01:00
Dan Brown
0d5d77d8ab Updated search test to fit with new tokenization 2017-10-15 19:24:06 +01:00
Dan Brown
db51cee2d8 Prevented custom homepage being deleted
Fixes #546
2017-10-15 19:14:46 +01:00
Dan Brown
a988438946 Expanded list of indexing split chars
Expands on #531
2017-10-15 19:14:31 +01:00
Dan Brown
3bf7cac030 Prevented flexbox contains overflowing page
Fixes #552
2017-10-15 18:34:37 +01:00
Dan Brown
79c3a07e9a Fixed include syntax erroring within vue
Fixes #553
2017-10-15 18:20:13 +01:00
Dan Brown
16117d329c Merge branch 'master' into release, Updated version 2017-10-06 21:05:45 +01:00
Dan Brown
9758872baf Updated image fetching in exporting
Added domain check to see if possibly local even when whole url found.
Changed image fetch from file_get_contents to curl for external
resources.

Hopeful solution to #392
2017-10-06 20:49:25 +01:00
17 changed files with 112 additions and 58 deletions

View File

@@ -324,9 +324,10 @@ class PageController extends Controller
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$book = $page->book;
$this->checkOwnablePermission('page-delete', $page);
$this->entityRepo->destroyPage($page);
Activity::addMessage('page_delete', $book->id, $page->name);
session()->flash('success', trans('entities.pages_delete_success'));
$this->entityRepo->destroyPage($page);
return redirect($book->getUrl());
}

View File

@@ -4,6 +4,7 @@ use BookStack\Book;
use BookStack\Chapter;
use BookStack\Entity;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\NotifyException;
use BookStack\Page;
use BookStack\PageRevision;
use BookStack\Services\AttachmentService;
@@ -1073,6 +1074,7 @@ class EntityRepo
/**
* Destroy a given page along with its dependencies.
* @param Page $page
* @throws NotifyException
*/
public function destroyPage(Page $page)
{
@@ -1084,6 +1086,12 @@ class EntityRepo
$this->permissionService->deleteJointPermissionsForEntity($page);
$this->searchService->deleteEntityTerms($page);
// Check if set as custom homepage
$customHome = setting('app-homepage', '0:');
if (intval($page->id) === intval(explode(':', $customHome)[0])) {
throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl());
}
// Delete Attached Files
$attachmentService = app(AttachmentService::class);
foreach ($page->attachments as $attachment) {

View File

@@ -136,6 +136,7 @@ class ExportService
* Bundle of the contents of a html file to be self-contained.
* @param $htmlContent
* @return mixed|string
* @throws \Exception
*/
protected function containHtml($htmlContent)
{
@@ -153,9 +154,27 @@ class ExportService
} else {
$pathString = $srcString;
}
// Attempt to find local files even if url not absolute
$base = baseUrl('/');
if (strpos($srcString, $base) === 0) {
$isLocal = true;
$relString = str_replace($base, '', $srcString);
$pathString = public_path(trim($relString, '/'));
}
if ($isLocal && !file_exists($pathString)) continue;
try {
$imageContent = file_get_contents($pathString);
if ($isLocal) {
$imageContent = file_get_contents($pathString);
} else {
$ch = curl_init();
curl_setopt_array($ch, [CURLOPT_URL => $pathString, CURLOPT_RETURNTRANSFER => 1, CURLOPT_CONNECTTIMEOUT => 5]);
$imageContent = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err) throw new \Exception("Image fetch failed, Received error: " . $err);
}
$imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent);
$newImageString = str_replace($srcString, $imageEncoded, $oldImgString);
} catch (\ErrorException $e) {

View File

@@ -382,7 +382,7 @@ class SearchService
protected function generateTermArrayFromText($text, $scoreAdjustment = 1)
{
$tokenMap = []; // {TextToken => OccurrenceCount}
$splitChars = " \n\t.,";
$splitChars = " \n\t.,!?:;()[]{}<>`'\"";
$token = strtok($text, $splitChars);
while ($token !== false) {

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

@@ -19,6 +19,7 @@ body.flexbox {
display: flex;
align-items: stretch;
min-height: 0;
max-width: 100%;
position: relative;
&.rows {
flex-direction: row;

View File

@@ -41,6 +41,7 @@ return [
// Pages
'page_draft_autosave_fail' => 'Failed to save draft. Ensure you have internet connection before saving this page',
'page_custom_home_deletion' => 'Cannot delete a page while it is set as a homepage',
// Entities
'entity_not_found' => 'Entity not found',

View File

@@ -91,7 +91,7 @@
@section('body')
<div ng-non-bindable class="container small">
<div class="container small">
<h1>{{$book->name}}</h1>
<div class="book-content" v-show="!searching">
<p class="text-muted" v-pre>{!! nl2br(e($book->description)) !!}</p>

View File

@@ -1,19 +1,19 @@
@extends('sidebar-layout')
@section('toolbar')
<div class="col-sm-6 col-xs-3 faded" ng-non-bindable>
<div class="col-sm-6 col-xs-3 faded" v-pre>
@include('chapters._breadcrumbs', ['chapter' => $chapter])
</div>
<div class="col-sm-6 col-xs-9 faded">
<div class="action-buttons">
<span dropdown class="dropdown-container">
<div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.export') }}</div>
<ul class="wide">
<li><a href="{{ $chapter->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
<li><a href="{{ $chapter->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
<li><a href="{{ $chapter->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
</ul>
</span>
<span dropdown class="dropdown-container">
<div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.export') }}</div>
<ul class="wide">
<li><a href="{{ $chapter->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
<li><a href="{{ $chapter->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
<li><a href="{{ $chapter->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
</ul>
</span>
@if(userCan('page-create', $chapter))
<a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a>
@endif
@@ -96,13 +96,13 @@
@section('body')
<div class="container small" ng-non-bindable >
<h1>{{ $chapter->name }}</h1>
<div class="container small">
<h1 v-pre>{{ $chapter->name }}</h1>
<div class="chapter-content" v-show="!searching">
<p class="text-muted">{!! nl2br(e($chapter->description)) !!}</p>
<p v-pre class="text-muted">{!! nl2br(e($chapter->description)) !!}</p>
@if(count($pages) > 0)
<div class="page-list">
<div v-pre class="page-list">
<hr>
@foreach($pages as $page)
@include('pages/list-item', ['page' => $page])
@@ -110,7 +110,7 @@
@endforeach
</div>
@else
<div class="well">
<div v-pre class="well">
<p class="text-muted italic">{{ trans('entities.chapters_empty') }}</p>
<p>
@if(userCan('page-create', $chapter))

View File

@@ -51,7 +51,7 @@
</div>
{{--Title input--}}
<div class="title-input page-title clearfix" ng-non-bindable>
<div class="title-input page-title clearfix" v-pre>
<div class="input">
@include('form/text', ['name' => 'name', 'placeholder' => trans('entities.pages_title')])
</div>
@@ -63,7 +63,7 @@
{{--WYSIWYG Editor--}}
@if(setting('app-editor') === 'wysiwyg')
<div wysiwyg-editor class="flex-fill flex">
<textarea id="html-editor" name="html" rows="5" ng-non-bindable
<textarea id="html-editor" name="html" rows="5" v-pre
@if($errors->has('html')) class="neg" @endif>@if(isset($model) || old('html')){{htmlspecialchars( old('html') ? old('html') : $model->html)}}@endif</textarea>
</div>
@@ -74,7 +74,7 @@
{{--Markdown Editor--}}
@if(setting('app-editor') === 'markdown')
<div ng-non-bindable id="markdown-editor" markdown-editor class="flex-fill flex code-fill">
<div v-pre id="markdown-editor" markdown-editor class="flex-fill flex code-fill">
<div class="markdown-editor-wrap">
<div class="editor-toolbar">

View File

@@ -7,7 +7,7 @@
</div>
@endif
<div class="right" ng-non-bindable>
<div class="right" v-pre>
@if($activity->user)
<a href="{{ $activity->user->getProfileUrl() }}">{{ $activity->user->name }}</a>
@else

View File

@@ -1,4 +1,4 @@
<div class="card book-tree" ng-non-bindable>
<div class="card book-tree" v-pre>
<h3><i class="zmdi zmdi-book"></i> {{ trans('entities.books_navigation') }}</h3>
<div class="body">
<ul class="sidebar-page-list menu">

View File

@@ -33,13 +33,13 @@ class EntitySearchTest extends TestCase
public function test_searching_accents_and_small_terms()
{
$page = $this->newPage(['name' => 'My new test quaffleachits', 'html' => 'some áéííúü¿¡ test content {a2 orange dog']);
$page = $this->newPage(['name' => 'My new test quaffleachits', 'html' => 'some áéííúü¿¡ test content a2 orange dog']);
$this->asEditor();
$accentSearch = $this->get('/search?term=' . urlencode('áéíí'));
$accentSearch->assertStatus(200)->assertSee($page->name);
$smallSearch = $this->get('/search?term=' . urlencode('{a'));
$smallSearch = $this->get('/search?term=' . urlencode('a2'));
$smallSearch->assertStatus(200)->assertSee($page->name);
}

View File

@@ -16,7 +16,8 @@ class HomepageTest extends TestCase
$homeVisit->assertSee('Recent Activity');
}
public function test_custom_homepage() {
public function test_custom_homepage()
{
$this->asEditor();
$name = 'My custom homepage';
$content = 'This is the body content of my custom homepage.';
@@ -30,4 +31,26 @@ class HomepageTest extends TestCase
$homeVisit->assertSee('Recently Updated Pages');
$homeVisit->assertSee('Recent Activity');
}
public function test_delete_custom_homepage()
{
$this->asEditor();
$name = 'My custom homepage';
$content = 'This is the body content of my custom homepage.';
$customPage = $this->newPage(['name' => $name, 'html' => $content]);
$this->setSettings(['app-homepage' => $customPage->id]);
$homeVisit = $this->get('/');
$homeVisit->assertSee($name);
$pageDeleteReq = $this->delete($customPage->getUrl());
$pageDeleteReq->assertStatus(302);
$pageDeleteReq->assertRedirect($customPage->getUrl());
$pageDeleteReq->assertSessionHas('error');
$pageDeleteReq->assertSessionMissing('success');
$homeVisit = $this->get('/');
$homeVisit->assertSee($name);
$homeVisit->assertStatus(200);
}
}

View File

@@ -1 +1 @@
v0.18.2
v0.18.5