mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Compare commits
14 Commits
2c11820a65
...
e09905a03d
Author | SHA1 | Date | |
---|---|---|---|
|
e09905a03d | ||
|
f583354748 | ||
|
d12e8ec923 | ||
|
89f84c9a95 | ||
|
6103a22feb | ||
|
42264f402d | ||
|
abda9bc00a | ||
|
eec639d84e | ||
|
fde4962ce3 | ||
|
25019efda2 | ||
|
d14d3e607d | ||
|
4f890c431c | ||
|
c110a97d8a | ||
|
476c2be5a6 |
@ -334,6 +334,11 @@ EXPORT_PAGE_SIZE=a4
|
||||
# Example: EXPORT_PDF_COMMAND="/scripts/convert.sh {input_html_path} {output_pdf_path}"
|
||||
EXPORT_PDF_COMMAND=false
|
||||
|
||||
# Export PDF Command Timeout
|
||||
# The number of seconds that the export PDF command will run before a timeout occurs.
|
||||
# Only applies for the EXPORT_PDF_COMMAND option, not for DomPDF or wkhtmltopdf.
|
||||
EXPORT_PDF_COMMAND_TIMEOUT=15
|
||||
|
||||
# Set path to wkhtmltopdf binary for PDF generation.
|
||||
# Can be 'false' or a path path like: '/home/bins/wkhtmltopdf'
|
||||
# When false, BookStack will attempt to find a wkhtmltopdf in the application
|
||||
|
10
app/Access/UserInviteException.php
Normal file
10
app/Access/UserInviteException.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
|
||||
use Exception;
|
||||
|
||||
class UserInviteException extends Exception
|
||||
{
|
||||
//
|
||||
}
|
@ -13,11 +13,17 @@ class UserInviteService extends UserTokenService
|
||||
/**
|
||||
* Send an invitation to a user to sign into BookStack
|
||||
* Removes existing invitation tokens.
|
||||
* @throws UserInviteException
|
||||
*/
|
||||
public function sendInvitation(User $user)
|
||||
{
|
||||
$this->deleteByUser($user);
|
||||
$token = $this->createTokenForUser($user);
|
||||
$user->notify(new UserInviteNotification($token));
|
||||
|
||||
try {
|
||||
$user->notify(new UserInviteNotification($token));
|
||||
} catch (\Exception $exception) {
|
||||
throw new UserInviteException($exception->getMessage(), $exception->getCode(), $exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,4 +64,14 @@ class MetaController extends Controller
|
||||
'jsLibData' => file_get_contents(base_path('dev/licensing/js-library-licenses.txt')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the view for /opensearch.xml.
|
||||
*/
|
||||
public function opensearch()
|
||||
{
|
||||
return response()
|
||||
->view('misc.opensearch')
|
||||
->header('Content-Type', 'application/opensearchdescription+xml');
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,10 @@ return [
|
||||
// Example: EXPORT_PDF_COMMAND="/scripts/convert.sh {input_html_path} {output_pdf_path}"
|
||||
'pdf_command' => env('EXPORT_PDF_COMMAND', false),
|
||||
|
||||
// The amount of time allowed for PDF generation command to run
|
||||
// before the process times out and is stopped.
|
||||
'pdf_command_timeout' => env('EXPORT_PDF_COMMAND_TIMEOUT', 15),
|
||||
|
||||
// 2024-04: Snappy/WKHTMLtoPDF now considered deprecated in regard to BookStack support.
|
||||
'snappy' => [
|
||||
'pdf_binary' => env('WKHTMLTOPDF', false),
|
||||
|
@ -11,7 +11,6 @@ use BookStack\Entities\Models\PageRevision;
|
||||
use BookStack\Entities\Queries\EntityQueries;
|
||||
use BookStack\Entities\Tools\BookContents;
|
||||
use BookStack\Entities\Tools\PageContent;
|
||||
use BookStack\Entities\Tools\PageEditorData;
|
||||
use BookStack\Entities\Tools\PageEditorType;
|
||||
use BookStack\Entities\Tools\TrashCan;
|
||||
use BookStack\Exceptions\MoveOperationException;
|
||||
@ -44,6 +43,7 @@ class PageRepo
|
||||
'owned_by' => user()->id,
|
||||
'updated_by' => user()->id,
|
||||
'draft' => true,
|
||||
'editor' => PageEditorType::getSystemDefault()->value,
|
||||
]);
|
||||
|
||||
if ($parent instanceof Chapter) {
|
||||
@ -146,8 +146,10 @@ class PageRepo
|
||||
$pageContent->setNewHTML($input['html'], user());
|
||||
}
|
||||
|
||||
if ($newEditor !== $currentEditor && userCan('editor-change')) {
|
||||
if (($newEditor !== $currentEditor || empty($page->editor)) && userCan('editor-change')) {
|
||||
$page->editor = $newEditor->value;
|
||||
} elseif (empty($page->editor)) {
|
||||
$page->editor = $defaultEditor->value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ namespace BookStack\Entities\Tools;
|
||||
use BookStack\Exceptions\PdfExportException;
|
||||
use Knp\Snappy\Pdf as SnappyPdf;
|
||||
use Dompdf\Dompdf;
|
||||
use Symfony\Component\Process\Exception\ProcessTimedOutException;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class PdfGenerator
|
||||
@ -85,9 +86,15 @@ class PdfGenerator
|
||||
|
||||
file_put_contents($inputHtml, $html);
|
||||
|
||||
$timeout = intval(config('exports.pdf_command_timeout'));
|
||||
$process = Process::fromShellCommandline($command);
|
||||
$process->setTimeout(15);
|
||||
$process->run();
|
||||
$process->setTimeout($timeout);
|
||||
|
||||
try {
|
||||
$process->run();
|
||||
} catch (ProcessTimedOutException $e) {
|
||||
throw new PdfExportException("PDF Export via command failed due to timeout at {$timeout} second(s)");
|
||||
}
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
throw new PdfExportException("PDF Export via command failed with exit code {$process->getExitCode()}, stdout: {$process->getOutput()}, stderr: {$process->getErrorOutput()}");
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace BookStack\Users\Controllers;
|
||||
|
||||
use BookStack\Access\SocialDriverManager;
|
||||
use BookStack\Access\UserInviteException;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Exceptions\UserUpdateException;
|
||||
use BookStack\Http\Controller;
|
||||
@ -14,6 +15,7 @@ use BookStack\Util\SimpleListOptions;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
@ -91,9 +93,15 @@ class UserController extends Controller
|
||||
|
||||
$validated = $this->validate($request, array_filter($validationRules));
|
||||
|
||||
DB::transaction(function () use ($validated, $sendInvite) {
|
||||
$this->userRepo->create($validated, $sendInvite);
|
||||
});
|
||||
try {
|
||||
DB::transaction(function () use ($validated, $sendInvite) {
|
||||
$this->userRepo->create($validated, $sendInvite);
|
||||
});
|
||||
} catch (UserInviteException $e) {
|
||||
Log::error("Failed to send user invite with error: {$e->getMessage()}");
|
||||
$this->showErrorNotification(trans('errors.users_could_not_send_invite'));
|
||||
return redirect('/settings/users/create')->withInput();
|
||||
}
|
||||
|
||||
return redirect('/settings/users');
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace BookStack\Users;
|
||||
|
||||
use BookStack\Access\UserInviteException;
|
||||
use BookStack\Access\UserInviteService;
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Entities\EntityProvider;
|
||||
@ -83,6 +84,7 @@ class UserRepo
|
||||
* As per "createWithoutActivity" but records a "create" activity.
|
||||
*
|
||||
* @param array{name: string, email: string, password: ?string, external_auth_id: ?string, language: ?string, roles: ?array} $data
|
||||
* @throws UserInviteException
|
||||
*/
|
||||
public function create(array $data, bool $sendInvite = false): User
|
||||
{
|
||||
|
@ -16,9 +16,9 @@
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-xml": "*",
|
||||
"bacon/bacon-qr-code": "^2.0",
|
||||
"bacon/bacon-qr-code": "^3.0",
|
||||
"doctrine/dbal": "^3.5",
|
||||
"dompdf/dompdf": "^2.0",
|
||||
"dompdf/dompdf": "^3.0",
|
||||
"guzzlehttp/guzzle": "^7.4",
|
||||
"intervention/image": "^3.5",
|
||||
"knplabs/knp-snappy": "^1.5",
|
||||
|
227
composer.lock
generated
227
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "97259e40ffe5518cfcdf1e32eacbb175",
|
||||
"content-hash": "b24a1daf815b6910b51a2acc5e2d38e7",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-crt-php",
|
||||
@ -160,28 +160,28 @@
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
"version": "2.0.8",
|
||||
"version": "v3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Bacon/BaconQrCode.git",
|
||||
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22"
|
||||
"reference": "510de6eca6248d77d31b339d62437cc995e2fb41"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22",
|
||||
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22",
|
||||
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/510de6eca6248d77d31b339d62437cc995e2fb41",
|
||||
"reference": "510de6eca6248d77d31b339d62437cc995e2fb41",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dasprid/enum": "^1.0.3",
|
||||
"ext-iconv": "*",
|
||||
"php": "^7.1 || ^8.0"
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phly/keep-a-changelog": "^2.1",
|
||||
"phpunit/phpunit": "^7 | ^8 | ^9",
|
||||
"spatie/phpunit-snapshot-assertions": "^4.2.9",
|
||||
"squizlabs/php_codesniffer": "^3.4"
|
||||
"phly/keep-a-changelog": "^2.12",
|
||||
"phpunit/phpunit": "^10.5.11 || 11.0.4",
|
||||
"spatie/phpunit-snapshot-assertions": "^5.1.5",
|
||||
"squizlabs/php_codesniffer": "^3.9"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-imagick": "to generate QR code images"
|
||||
@ -208,9 +208,9 @@
|
||||
"homepage": "https://github.com/Bacon/BaconQrCode",
|
||||
"support": {
|
||||
"issues": "https://github.com/Bacon/BaconQrCode/issues",
|
||||
"source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8"
|
||||
"source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.0"
|
||||
},
|
||||
"time": "2022-12-07T17:46:57+00:00"
|
||||
"time": "2024-04-18T11:16:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
@ -980,32 +980,34 @@
|
||||
},
|
||||
{
|
||||
"name": "dompdf/dompdf",
|
||||
"version": "v2.0.8",
|
||||
"version": "v3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/dompdf.git",
|
||||
"reference": "c20247574601700e1f7c8dab39310fca1964dc52"
|
||||
"reference": "fbc7c5ee5d94f7a910b78b43feb7931b7f971b59"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/c20247574601700e1f7c8dab39310fca1964dc52",
|
||||
"reference": "c20247574601700e1f7c8dab39310fca1964dc52",
|
||||
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/fbc7c5ee5d94f7a910b78b43feb7931b7f971b59",
|
||||
"reference": "fbc7c5ee5d94f7a910b78b43feb7931b7f971b59",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dompdf/php-font-lib": "^1.0.0",
|
||||
"dompdf/php-svg-lib": "^1.0.0",
|
||||
"ext-dom": "*",
|
||||
"ext-mbstring": "*",
|
||||
"masterminds/html5": "^2.0",
|
||||
"phenx/php-font-lib": ">=0.5.4 <1.0.0",
|
||||
"phenx/php-svg-lib": ">=0.5.2 <1.0.0",
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-gd": "*",
|
||||
"ext-json": "*",
|
||||
"ext-zip": "*",
|
||||
"mockery/mockery": "^1.3",
|
||||
"phpunit/phpunit": "^7.5 || ^8 || ^9",
|
||||
"squizlabs/php_codesniffer": "^3.5"
|
||||
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-gd": "Needed to process images",
|
||||
@ -1036,9 +1038,100 @@
|
||||
"homepage": "https://github.com/dompdf/dompdf",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/dompdf/issues",
|
||||
"source": "https://github.com/dompdf/dompdf/tree/v2.0.8"
|
||||
"source": "https://github.com/dompdf/dompdf/tree/v3.0.0"
|
||||
},
|
||||
"time": "2024-04-29T13:06:17+00:00"
|
||||
"time": "2024-04-29T14:01:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dompdf/php-font-lib",
|
||||
"version": "1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/php-font-lib.git",
|
||||
"reference": "991d6a954f6bbd7e41022198f00586b230731441"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/991d6a954f6bbd7e41022198f00586b230731441",
|
||||
"reference": "991d6a954f6bbd7e41022198f00586b230731441",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FontLib\\": "src/FontLib"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The FontLib Community",
|
||||
"homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
|
||||
}
|
||||
],
|
||||
"description": "A library to read, parse, export and make subsets of different types of font files.",
|
||||
"homepage": "https://github.com/dompdf/php-font-lib",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/php-font-lib/issues",
|
||||
"source": "https://github.com/dompdf/php-font-lib/tree/1.0.0"
|
||||
},
|
||||
"time": "2024-04-29T13:40:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dompdf/php-svg-lib",
|
||||
"version": "1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/php-svg-lib.git",
|
||||
"reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af",
|
||||
"reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.1 || ^8.0",
|
||||
"sabberworm/php-css-parser": "^8.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Svg\\": "src/Svg"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-3.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The SvgLib Community",
|
||||
"homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
|
||||
}
|
||||
],
|
||||
"description": "A library to read, parse and export to PDF SVG files.",
|
||||
"homepage": "https://github.com/dompdf/php-svg-lib",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/php-svg-lib/issues",
|
||||
"source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0"
|
||||
},
|
||||
"time": "2024-04-29T13:26:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dragonmantank/cron-expression",
|
||||
@ -3930,96 +4023,6 @@
|
||||
},
|
||||
"time": "2020-10-15T08:29:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phenx/php-font-lib",
|
||||
"version": "0.5.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/php-font-lib.git",
|
||||
"reference": "a1681e9793040740a405ac5b189275059e2a9863"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a1681e9793040740a405ac5b189275059e2a9863",
|
||||
"reference": "a1681e9793040740a405ac5b189275059e2a9863",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FontLib\\": "src/FontLib"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Ménager",
|
||||
"email": "fabien.menager@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A library to read, parse, export and make subsets of different types of font files.",
|
||||
"homepage": "https://github.com/PhenX/php-font-lib",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/php-font-lib/issues",
|
||||
"source": "https://github.com/dompdf/php-font-lib/tree/0.5.6"
|
||||
},
|
||||
"time": "2024-01-29T14:45:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phenx/php-svg-lib",
|
||||
"version": "0.5.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/php-svg-lib.git",
|
||||
"reference": "46b25da81613a9cf43c83b2a8c2c1bdab27df691"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/46b25da81613a9cf43c83b2a8c2c1bdab27df691",
|
||||
"reference": "46b25da81613a9cf43c83b2a8c2c1bdab27df691",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.1 || ^8.0",
|
||||
"sabberworm/php-css-parser": "^8.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Svg\\": "src/Svg"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-3.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Ménager",
|
||||
"email": "fabien.menager@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A library to read, parse and export to PDF SVG files.",
|
||||
"homepage": "https://github.com/PhenX/php-svg-lib",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/php-svg-lib/issues",
|
||||
"source": "https://github.com/dompdf/php-svg-lib/tree/0.5.4"
|
||||
},
|
||||
"time": "2024-04-08T12:52:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.3",
|
||||
|
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Ensure we have an "editor" value set for pages
|
||||
|
||||
// Get default
|
||||
$default = DB::table('settings')
|
||||
->where('setting_key', '=', 'app-editor')
|
||||
->first()
|
||||
->value ?? 'wysiwyg';
|
||||
$default = ($default === 'markdown') ? 'markdown' : 'wysiwyg';
|
||||
|
||||
// We set it to 'markdown' for pages currently with markdown content
|
||||
DB::table('pages')
|
||||
->where('editor', '=', '')
|
||||
->where('markdown', '!=', '')
|
||||
->update(['editor' => 'markdown']);
|
||||
|
||||
// We set it to 'wysiwyg' where we have HTML but no markdown
|
||||
DB::table('pages')
|
||||
->where('editor', '=', '')
|
||||
->where('markdown', '=', '')
|
||||
->where('html', '!=', '')
|
||||
->update(['editor' => 'wysiwyg']);
|
||||
|
||||
// Otherwise, where still empty, set to the current default
|
||||
DB::table('pages')
|
||||
->where('editor', '=', '')
|
||||
->update(['editor' => $default]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Can't reverse due to not knowing what would have been empty before
|
||||
}
|
||||
};
|
@ -107,4 +107,7 @@ return [
|
||||
// Not directly used but available for convenience to users.
|
||||
'privacy_policy' => 'Privacy Policy',
|
||||
'terms_of_service' => 'Terms of Service',
|
||||
|
||||
// OpenSearch
|
||||
'opensearch_description' => 'Search :appName',
|
||||
];
|
||||
|
@ -78,6 +78,7 @@ return [
|
||||
// Users
|
||||
'users_cannot_delete_only_admin' => 'You cannot delete the only admin',
|
||||
'users_cannot_delete_guest' => 'You cannot delete the guest user',
|
||||
'users_could_not_send_invite' => 'Could not create user since invite email failed to send',
|
||||
|
||||
// Roles
|
||||
'role_cannot_be_edited' => 'This role cannot be edited',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as DrawIO from '../services/drawio';
|
||||
import * as DrawIO from '../services/drawio.ts';
|
||||
|
||||
export class Actions {
|
||||
|
||||
|
@ -163,29 +163,29 @@ body .page-content img,
|
||||
}
|
||||
&.success {
|
||||
@include lightDark(border-color, $positive, $positive-dark);
|
||||
@include lightDark(background-color, lighten($positive, 68%), darken($positive-dark, 36%));
|
||||
@include lightDark(color, darken($positive, 16%), $positive-dark);
|
||||
@include lightDark(background-color, #eafdeb, #122913);
|
||||
@include lightDark(color, #063409, $positive-dark);
|
||||
}
|
||||
&.success:before {
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiMzNzZjMzkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bS0yIDE1bC01LTUgMS40MS0xLjQxTDEwIDE0LjE3bDcuNTktNy41OUwxOSA4bC05IDl6Ii8+PC9zdmc+");
|
||||
}
|
||||
&.danger {
|
||||
@include lightDark(border-color, $negative, $negative-dark);
|
||||
@include lightDark(background-color, lighten($negative, 56%), darken($negative-dark, 55%));
|
||||
@include lightDark(color, darken($negative, 20%), $negative-dark);
|
||||
@include lightDark(background-color, #fcdbdb, #250505);
|
||||
@include lightDark(color, #4d0706, $negative-dark);
|
||||
}
|
||||
&.danger:before {
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiNiOTE4MTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0xNS43MyAzSDguMjdMMyA4LjI3djcuNDZMOC4yNyAyMWg3LjQ2TDIxIDE1LjczVjguMjdMMTUuNzMgM3pNMTIgMTcuM2MtLjcyIDAtMS4zLS41OC0xLjMtMS4zIDAtLjcyLjU4LTEuMyAxLjMtMS4zLjcyIDAgMS4zLjU4IDEuMyAxLjMgMCAuNzItLjU4IDEuMy0xLjMgMS4zem0xLTQuM2gtMlY3aDJ2NnoiLz4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg==");
|
||||
}
|
||||
&.info {
|
||||
@include lightDark(border-color, $info, $info-dark);
|
||||
@include lightDark(color, darken($info, 20%), $info-dark);
|
||||
@include lightDark(background-color, lighten($info, 50%), darken($info-dark, 34%));
|
||||
@include lightDark(background-color, #d3efff, #001825);
|
||||
@include lightDark(color, #01466c, $info-dark);
|
||||
}
|
||||
&.warning {
|
||||
@include lightDark(border-color, $warning, $warning-dark);
|
||||
@include lightDark(background-color, lighten($warning, 50%), darken($warning-dark, 50%));
|
||||
@include lightDark(color, darken($warning, 20%), $warning-dark);
|
||||
@include lightDark(background-color, #fee3d3, #30170a);
|
||||
@include lightDark(color, #6a2802, $warning-dark);
|
||||
}
|
||||
&.warning:before {
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiNiNjUzMWMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEgMjFoMjJMMTIgMiAxIDIxem0xMi0zaC0ydi0yaDJ2MnptMC00aC0ydi00aDJ2NHoiLz48L3N2Zz4=");
|
||||
|
@ -441,12 +441,8 @@ input[type=color] {
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 9px;
|
||||
@include rtl {
|
||||
right: 8px;
|
||||
left: auto;
|
||||
}
|
||||
inset-inline-start: 8px;
|
||||
top: 10px;
|
||||
}
|
||||
input {
|
||||
display: block;
|
||||
|
@ -44,11 +44,6 @@ h1, h2, h3, h4, h5, h6 {
|
||||
display: block;
|
||||
font-family: var(--font-heading, var(--font-body));
|
||||
@include lightDark(color, #222, #BBB);
|
||||
.subheader {
|
||||
font-size: 0.5em;
|
||||
line-height: 1em;
|
||||
color: lighten($text-dark, 32%);
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
@ -223,7 +218,7 @@ blockquote {
|
||||
position: absolute;
|
||||
top: $-s;
|
||||
left: $-s;
|
||||
color: lighten($text-dark, 20%);
|
||||
color: #777;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,9 @@
|
||||
<link rel="manifest" href="{{ url('/manifest.json') }}">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
|
||||
<!-- OpenSearch -->
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="{{ setting('app-name') }}" href="{{ url('/opensearch.xml') }}">
|
||||
|
||||
@yield('head')
|
||||
|
||||
<!-- Custom Styles & Head Content -->
|
||||
|
12
resources/views/misc/opensearch.blade.php
Normal file
12
resources/views/misc/opensearch.blade.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
|
||||
<ShortName>{{ mb_strimwidth(setting('app-name'), 0, 16) }}</ShortName>
|
||||
<Description>{{ trans('common.opensearch_description', ['appName' => setting('app-name')]) }}</Description>
|
||||
<Image width="256" height="256" type="image/png">{{ setting('app-icon') ?: url('/icon.png') }}</Image>
|
||||
<Image width="180" height="180" type="image/png">{{ setting('app-icon-180') ?: url('/icon-180.png') }}</Image>
|
||||
<Image width="128" height="128" type="image/png">{{ setting('app-icon-128') ?: url('/icon-128.png') }}</Image>
|
||||
<Image width="64" height="64" type="image/png">{{ setting('app-icon-64') ?: url('/icon-64.png') }}</Image>
|
||||
<Image width="32" height="32" type="image/png">{{ setting('app-icon-32') ?: url('/icon-32.png') }}</Image>
|
||||
<Url type="text/html" rel="results" template="{{ url('/search') }}?term={searchTerms}"/>
|
||||
<Url type="application/opensearchdescription+xml" rel="self" template="{{ url('/opensearch.xml') }}"/>
|
||||
</OpenSearchDescription>
|
@ -79,7 +79,9 @@
|
||||
|
||||
<form action="{{ url('/search') }}" method="GET" class="search-box flexible hide-over-l">
|
||||
<input value="{{$searchTerm}}" type="text" name="term" placeholder="{{ trans('common.search') }}">
|
||||
<button type="submit">@icon('search')</button>
|
||||
<button type="submit"
|
||||
aria-label="{{ trans('common.search') }}"
|
||||
tabindex="-1">@icon('search')</button>
|
||||
</form>
|
||||
|
||||
<h6 class="text-muted">{{ trans_choice('entities.search_total_results_found', $totalResults, ['count' => $totalResults]) }}</h6>
|
||||
|
@ -23,6 +23,7 @@ Route::get('/robots.txt', [MetaController::class, 'robots']);
|
||||
Route::get('/favicon.ico', [MetaController::class, 'favicon']);
|
||||
Route::get('/manifest.json', [MetaController::class, 'pwaManifest']);
|
||||
Route::get('/licenses', [MetaController::class, 'licenses']);
|
||||
Route::get('/opensearch.xml', [MetaController::class, 'opensearch']);
|
||||
|
||||
// Authenticated routes...
|
||||
Route::middleware('auth')->group(function () {
|
||||
|
3
storage/framework/cache/.gitignore
vendored
3
storage/framework/cache/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
*
|
||||
!data/
|
||||
!.gitignore
|
2
storage/framework/views/.gitignore
vendored
2
storage/framework/views/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
@ -529,6 +529,22 @@ class ExportTest extends TestCase
|
||||
}, PdfExportException::class);
|
||||
}
|
||||
|
||||
public function test_pdf_command_timout_option_limits_export_time()
|
||||
{
|
||||
$page = $this->entities->page();
|
||||
$command = 'php -r \'sleep(4);\'';
|
||||
config()->set('exports.pdf_command', $command);
|
||||
config()->set('exports.pdf_command_timeout', 1);
|
||||
|
||||
$this->assertThrows(function () use ($page) {
|
||||
$start = time();
|
||||
$this->withoutExceptionHandling()->asEditor()->get($page->getUrl('/export/pdf'));
|
||||
|
||||
$this->assertTrue(time() < ($start + 3));
|
||||
}, PdfExportException::class,
|
||||
"PDF Export via command failed due to timeout at 1 second(s)");
|
||||
}
|
||||
|
||||
public function test_html_exports_contain_csp_meta_tag()
|
||||
{
|
||||
$entities = [
|
||||
|
@ -24,6 +24,21 @@ class PageEditorTest extends TestCase
|
||||
$this->withHtml($this->followRedirects($resp))->assertElementExists('#html-editor');
|
||||
}
|
||||
|
||||
public function test_editor_set_for_new_pages()
|
||||
{
|
||||
$book = $this->page->book;
|
||||
|
||||
$this->asEditor()->get($book->getUrl('/create-page'));
|
||||
$newPage = $book->pages()->orderBy('id', 'desc')->first();
|
||||
$this->assertEquals('wysiwyg', $newPage->editor);
|
||||
|
||||
$this->setSettings(['app-editor' => PageEditorType::Markdown->value]);
|
||||
|
||||
$this->asEditor()->get($book->getUrl('/create-page'));
|
||||
$newPage = $book->pages()->orderBy('id', 'desc')->first();
|
||||
$this->assertEquals('markdown', $newPage->editor);
|
||||
}
|
||||
|
||||
public function test_markdown_setting_shows_markdown_editor_for_new_pages()
|
||||
{
|
||||
$this->setSettings(['app-editor' => PageEditorType::Markdown->value]);
|
||||
|
42
tests/OpensearchTest.php
Normal file
42
tests/OpensearchTest.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Tests;
|
||||
|
||||
class OpensearchTest extends TestCase
|
||||
{
|
||||
public function test_opensearch_endpoint()
|
||||
{
|
||||
$appName = setting('app-name');
|
||||
$resultUrl = url('/search') . '?term={searchTerms}';
|
||||
$selfUrl = url('/opensearch.xml');
|
||||
|
||||
$resp = $this->get('/opensearch.xml');
|
||||
$resp->assertOk();
|
||||
|
||||
$html = $this->withHtml($resp);
|
||||
|
||||
$html->assertElementExists('OpenSearchDescription > ShortName');
|
||||
$html->assertElementContains('OpenSearchDescription > ShortName', mb_strimwidth($appName, 0, 16));
|
||||
|
||||
$html->assertElementExists('OpenSearchDescription > Description');
|
||||
$html->assertElementContains('OpenSearchDescription > Description', trans('common.opensearch_description', [
|
||||
'appName' => $appName,
|
||||
]));
|
||||
|
||||
$html->assertElementExists('OpenSearchDescription > Image');
|
||||
|
||||
$html->assertElementExists('OpenSearchDescription > Url[rel="results"][template="' . htmlspecialchars($resultUrl) . '"]');
|
||||
$html->assertElementExists('OpenSearchDescription > Url[rel="self"][template="' . htmlspecialchars($selfUrl) . '"]');
|
||||
}
|
||||
|
||||
public function test_opensearch_linked_to_from_home()
|
||||
{
|
||||
$appName = setting('app-name');
|
||||
$endpointUrl = url('/opensearch.xml');
|
||||
|
||||
$resp = $this->asViewer()->get('/');
|
||||
$html = $this->withHtml($resp);
|
||||
|
||||
$html->assertElementExists('head > link[rel="search"][type="application/opensearchdescription+xml"][title="' . htmlspecialchars($appName) . '"][href="' . htmlspecialchars($endpointUrl) . '"]');
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests\User;
|
||||
|
||||
use BookStack\Access\UserInviteException;
|
||||
use BookStack\Access\UserInviteService;
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Uploads\Image;
|
||||
@ -229,7 +230,7 @@ class UserManagementTest extends TestCase
|
||||
|
||||
// Simulate an invitation sending failure
|
||||
$this->mock(UserInviteService::class, function (MockInterface $mock) {
|
||||
$mock->shouldReceive('sendInvitation')->once()->andThrow(RuntimeException::class);
|
||||
$mock->shouldReceive('sendInvitation')->once()->andThrow(UserInviteException::class);
|
||||
});
|
||||
|
||||
$this->asAdmin()->post('/settings/users/create', [
|
||||
@ -247,22 +248,42 @@ class UserManagementTest extends TestCase
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = User::factory()->make();
|
||||
$adminRole = Role::getRole('admin');
|
||||
|
||||
$this->mock(UserInviteService::class, function (MockInterface $mock) {
|
||||
$mock->shouldReceive('sendInvitation')->once()->andThrow(RuntimeException::class);
|
||||
$mock->shouldReceive('sendInvitation')->once()->andThrow(UserInviteException::class);
|
||||
});
|
||||
|
||||
$this->asAdmin()->post('/settings/users/create', [
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'send_invite' => 'true',
|
||||
'roles[' . $adminRole->id . ']' => 'true',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseMissing('activities', ['type' => 'USER_CREATE']);
|
||||
}
|
||||
|
||||
public function test_return_to_form_with_warning_if_the_invitation_sending_fails()
|
||||
{
|
||||
$logger = $this->withTestLogger();
|
||||
/** @var User $user */
|
||||
$user = User::factory()->make();
|
||||
|
||||
$this->mock(UserInviteService::class, function (MockInterface $mock) {
|
||||
$mock->shouldReceive('sendInvitation')->once()->andThrow(UserInviteException::class);
|
||||
});
|
||||
|
||||
$resp = $this->asAdmin()->post('/settings/users/create', [
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'send_invite' => 'true',
|
||||
]);
|
||||
|
||||
$resp->assertRedirect('/settings/users/create');
|
||||
$this->assertSessionError('Could not create user since invite email failed to send');
|
||||
$this->assertEquals($user->email, session()->getOldInput('email'));
|
||||
$this->assertTrue($logger->hasErrorThatContains('Failed to send user invite with error:'));
|
||||
}
|
||||
|
||||
public function test_user_create_update_fails_if_locale_is_invalid()
|
||||
{
|
||||
$user = $this->users->editor();
|
||||
|
Loading…
Reference in New Issue
Block a user