Merge pull request #5400 from BookStackApp/laravel11

Laravel 11 Upgrade
This commit is contained in:
Dan Brown 2025-01-13 13:27:32 +00:00 committed by GitHub
commit b9751807e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1036 additions and 1235 deletions

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
strategy: strategy:
matrix: matrix:
php: ['8.1', '8.2', '8.3', '8.4'] php: ['8.2', '8.3', '8.4']
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@ -13,10 +13,10 @@ on:
jobs: jobs:
build: build:
if: ${{ github.ref != 'refs/heads/l10n_development' }} if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: ubuntu-22.04 runs-on: ubuntu-24.04
strategy: strategy:
matrix: matrix:
php: ['8.1', '8.2', '8.3', '8.4'] php: ['8.2', '8.3', '8.4']
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@ -8,27 +8,15 @@ use Illuminate\Database\Eloquent\Model;
class ExternalBaseUserProvider implements UserProvider class ExternalBaseUserProvider implements UserProvider
{ {
/** public function __construct(
* The user model. protected string $model
* ) {
* @var string
*/
protected $model;
/**
* LdapUserProvider constructor.
*/
public function __construct(string $model)
{
$this->model = $model;
} }
/** /**
* Create a new instance of the model. * Create a new instance of the model.
*
* @return Model
*/ */
public function createModel() public function createModel(): Model
{ {
$class = '\\' . ltrim($this->model, '\\'); $class = '\\' . ltrim($this->model, '\\');
@ -37,12 +25,8 @@ class ExternalBaseUserProvider implements UserProvider
/** /**
* Retrieve a user by their unique identifier. * Retrieve a user by their unique identifier.
*
* @param mixed $identifier
*
* @return Authenticatable|null
*/ */
public function retrieveById($identifier) public function retrieveById(mixed $identifier): ?Authenticatable
{ {
return $this->createModel()->newQuery()->find($identifier); return $this->createModel()->newQuery()->find($identifier);
} }
@ -50,12 +34,9 @@ class ExternalBaseUserProvider implements UserProvider
/** /**
* Retrieve a user by their unique identifier and "remember me" token. * Retrieve a user by their unique identifier and "remember me" token.
* *
* @param mixed $identifier
* @param string $token * @param string $token
*
* @return Authenticatable|null
*/ */
public function retrieveByToken($identifier, $token) public function retrieveByToken(mixed $identifier, $token): null
{ {
return null; return null;
} }
@ -75,12 +56,8 @@ class ExternalBaseUserProvider implements UserProvider
/** /**
* Retrieve a user by the given credentials. * Retrieve a user by the given credentials.
*
* @param array $credentials
*
* @return Authenticatable|null
*/ */
public function retrieveByCredentials(array $credentials) public function retrieveByCredentials(array $credentials): ?Authenticatable
{ {
// Search current user base by looking up a uid // Search current user base by looking up a uid
$model = $this->createModel(); $model = $this->createModel();
@ -92,15 +69,15 @@ class ExternalBaseUserProvider implements UserProvider
/** /**
* Validate a user against the given credentials. * Validate a user against the given credentials.
*
* @param Authenticatable $user
* @param array $credentials
*
* @return bool
*/ */
public function validateCredentials(Authenticatable $user, array $credentials) public function validateCredentials(Authenticatable $user, array $credentials): bool
{ {
// Should be done in the guard. // Should be done in the guard.
return false; return false;
} }
public function rehashPasswordIfRequired(Authenticatable $user, #[\SensitiveParameter] array $credentials, bool $force = false)
{
// No action to perform, any passwords are external in the auth system
}
} }

View File

@ -26,7 +26,6 @@ class Comment extends Model implements Loggable
use HasCreatorAndUpdater; use HasCreatorAndUpdater;
protected $fillable = ['parent_id']; protected $fillable = ['parent_id'];
protected $appends = ['created', 'updated'];
/** /**
* Get the entity that this comment belongs to. * Get the entity that this comment belongs to.
@ -54,22 +53,6 @@ class Comment extends Model implements Loggable
return $this->updated_at->timestamp > $this->created_at->timestamp; return $this->updated_at->timestamp > $this->created_at->timestamp;
} }
/**
* Get created date as a relative diff.
*/
public function getCreatedAttribute(): string
{
return $this->created_at->diffForHumans();
}
/**
* Get updated date as a relative diff.
*/
public function getUpdatedAttribute(): string
{
return $this->updated_at->diffForHumans();
}
public function logDescriptor(): string public function logDescriptor(): string
{ {
return "Comment #{$this->local_id} (ID: {$this->id}) for {$this->entity_type} (ID: {$this->entity_id})"; return "Comment #{$this->local_id} (ID: {$this->id}) for {$this->entity_type} (ID: {$this->entity_id})";

View File

@ -42,4 +42,12 @@ class EventServiceProvider extends ServiceProvider
{ {
return false; return false;
} }
/**
* Overrides the registration of Laravel's default email verification system
*/
protected function configureEmailVerification(): void
{
//
}
} }

View File

@ -1,37 +0,0 @@
<?php
/**
* Broadcasting configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// Default Broadcaster
// This option controls the default broadcaster that will be used by the
// framework when an event needs to be broadcast. This can be set to
// any of the connections defined in the "connections" array below.
'default' => 'null',
// Broadcast Connections
// Here you may define all of the broadcast connections that will be used
// to broadcast events to other systems or over websockets. Samples of
// each available type of connection are provided inside this array.
'connections' => [
// Default options removed since we don't use broadcasting.
'log' => [
'driver' => 'log',
],
'null' => [
'driver' => 'null',
],
],
];

View File

@ -35,10 +35,6 @@ return [
// Available caches stores // Available caches stores
'stores' => [ 'stores' => [
'apc' => [
'driver' => 'apc',
],
'array' => [ 'array' => [
'driver' => 'array', 'driver' => 'array',
'serialize' => false, 'serialize' => false,
@ -49,6 +45,7 @@ return [
'table' => 'cache', 'table' => 'cache',
'connection' => null, 'connection' => null,
'lock_connection' => null, 'lock_connection' => null,
'lock_table' => null,
], ],
'file' => [ 'file' => [

View File

@ -33,12 +33,14 @@ return [
'driver' => 'local', 'driver' => 'local',
'root' => public_path(), 'root' => public_path(),
'visibility' => 'public', 'visibility' => 'public',
'serve' => false,
'throw' => true, 'throw' => true,
], ],
'local_secure_attachments' => [ 'local_secure_attachments' => [
'driver' => 'local', 'driver' => 'local',
'root' => storage_path('uploads/files/'), 'root' => storage_path('uploads/files/'),
'serve' => false,
'throw' => true, 'throw' => true,
], ],
@ -46,6 +48,7 @@ return [
'driver' => 'local', 'driver' => 'local',
'root' => storage_path('uploads/images/'), 'root' => storage_path('uploads/images/'),
'visibility' => 'public', 'visibility' => 'public',
'serve' => false,
'throw' => true, 'throw' => true,
], ],

View File

@ -38,7 +38,7 @@ return [
'password' => env('MAIL_PASSWORD'), 'password' => env('MAIL_PASSWORD'),
'verify_peer' => env('MAIL_VERIFY_SSL', true), 'verify_peer' => env('MAIL_VERIFY_SSL', true),
'timeout' => null, 'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN'), 'local_domain' => null,
'tls_required' => ($mailEncryption === 'tls' || $mailEncryption === 'ssl'), 'tls_required' => ($mailEncryption === 'tls' || $mailEncryption === 'ssl'),
], ],
@ -64,12 +64,4 @@ return [
], ],
], ],
], ],
// Email markdown configuration
'markdown' => [
'theme' => 'default',
'paths' => [
resource_path('views/vendor/mail'),
],
],
]; ];

View File

@ -23,6 +23,7 @@ return [
'database' => [ 'database' => [
'driver' => 'database', 'driver' => 'database',
'connection' => null,
'table' => 'jobs', 'table' => 'jobs',
'queue' => 'default', 'queue' => 'default',
'retry_after' => 90, 'retry_after' => 90,

View File

@ -5,7 +5,6 @@ namespace BookStack\Uploads;
use BookStack\Exceptions\FileUploadException; use BookStack\Exceptions\FileUploadException;
use Exception; use Exception;
use Illuminate\Contracts\Filesystem\Filesystem as Storage; use Illuminate\Contracts\Filesystem\Filesystem as Storage;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Filesystem\FilesystemManager; use Illuminate\Filesystem\FilesystemManager;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str; use Illuminate\Support\Str;

View File

@ -8,7 +8,7 @@
"license": "MIT", "license": "MIT",
"type": "project", "type": "project",
"require": { "require": {
"php": "^8.1.0", "php": "^8.2.0",
"ext-curl": "*", "ext-curl": "*",
"ext-dom": "*", "ext-dom": "*",
"ext-fileinfo": "*", "ext-fileinfo": "*",
@ -18,12 +18,11 @@
"ext-xml": "*", "ext-xml": "*",
"ext-zip": "*", "ext-zip": "*",
"bacon/bacon-qr-code": "^3.0", "bacon/bacon-qr-code": "^3.0",
"doctrine/dbal": "^3.5",
"dompdf/dompdf": "^3.0", "dompdf/dompdf": "^3.0",
"guzzlehttp/guzzle": "^7.4", "guzzlehttp/guzzle": "^7.4",
"intervention/image": "^3.5", "intervention/image": "^3.5",
"knplabs/knp-snappy": "^1.5", "knplabs/knp-snappy": "^1.5",
"laravel/framework": "^10.48.23", "laravel/framework": "^v11.37",
"laravel/socialite": "^5.10", "laravel/socialite": "^5.10",
"laravel/tinker": "^2.8", "laravel/tinker": "^2.8",
"league/commonmark": "^2.3", "league/commonmark": "^2.3",
@ -40,17 +39,17 @@
"socialiteproviders/okta": "^4.2", "socialiteproviders/okta": "^4.2",
"socialiteproviders/twitch": "^5.3", "socialiteproviders/twitch": "^5.3",
"ssddanbrown/htmldiff": "^1.0.2", "ssddanbrown/htmldiff": "^1.0.2",
"ssddanbrown/symfony-mailer": "6.4.x-dev" "ssddanbrown/symfony-mailer": "7.2.x-dev"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.21", "fakerphp/faker": "^1.21",
"itsgoingd/clockwork": "^5.1", "itsgoingd/clockwork": "^5.1",
"mockery/mockery": "^1.5", "mockery/mockery": "^1.5",
"nunomaduro/collision": "^7.0", "nunomaduro/collision": "^8.1",
"larastan/larastan": "^2.7", "larastan/larastan": "^v3.0",
"phpunit/phpunit": "^10.0", "phpunit/phpunit": "^11.5",
"squizlabs/php_codesniffer": "^3.7", "squizlabs/php_codesniffer": "^3.7",
"ssddanbrown/asserthtml": "^3.0" "ssddanbrown/asserthtml": "^3.1"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -104,7 +103,7 @@
"preferred-install": "dist", "preferred-install": "dist",
"sort-packages": true, "sort-packages": true,
"platform": { "platform": {
"php": "8.1.0" "php": "8.2.0"
} }
}, },
"extra": { "extra": {

2032
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -26,25 +26,19 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
$sm = Schema::getConnection()->getDoctrineSchemaManager(); if (Schema::hasIndex('pages', 'search')) {
$prefix = DB::getTablePrefix();
$pages = $sm->introspectTable($prefix . 'pages');
$books = $sm->introspectTable($prefix . 'books');
$chapters = $sm->introspectTable($prefix . 'chapters');
if ($pages->hasIndex('search')) {
Schema::table('pages', function (Blueprint $table) { Schema::table('pages', function (Blueprint $table) {
$table->dropIndex('search'); $table->dropIndex('search');
}); });
} }
if ($books->hasIndex('search')) { if (Schema::hasIndex('books', 'search')) {
Schema::table('books', function (Blueprint $table) { Schema::table('books', function (Blueprint $table) {
$table->dropIndex('search'); $table->dropIndex('search');
}); });
} }
if ($chapters->hasIndex('search')) { if (Schema::hasIndex('chapters', 'search')) {
Schema::table('chapters', function (Blueprint $table) { Schema::table('chapters', function (Blueprint $table) {
$table->dropIndex('search'); $table->dropIndex('search');
}); });

View File

@ -26,25 +26,19 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
$sm = Schema::getConnection()->getDoctrineSchemaManager(); if (Schema::hasIndex('pages', 'name_search')) {
$prefix = DB::getTablePrefix();
$pages = $sm->introspectTable($prefix . 'pages');
$books = $sm->introspectTable($prefix . 'books');
$chapters = $sm->introspectTable($prefix . 'chapters');
if ($pages->hasIndex('name_search')) {
Schema::table('pages', function (Blueprint $table) { Schema::table('pages', function (Blueprint $table) {
$table->dropIndex('name_search'); $table->dropIndex('name_search');
}); });
} }
if ($books->hasIndex('name_search')) { if (Schema::hasIndex('books', 'name_search')) {
Schema::table('books', function (Blueprint $table) { Schema::table('books', function (Blueprint $table) {
$table->dropIndex('name_search'); $table->dropIndex('name_search');
}); });
} }
if ($chapters->hasIndex('name_search')) { if (Schema::hasIndex('chapters', 'name_search')) {
Schema::table('chapters', function (Blueprint $table) { Schema::table('chapters', function (Blueprint $table) {
$table->dropIndex('name_search'); $table->dropIndex('name_search');
}); });

View File

@ -25,27 +25,21 @@ return new class extends Migration
$table->index('score'); $table->index('score');
}); });
$sm = Schema::getConnection()->getDoctrineSchemaManager(); if (Schema::hasIndex('pages', 'search')) {
$prefix = DB::getTablePrefix();
$pages = $sm->introspectTable($prefix . 'pages');
$books = $sm->introspectTable($prefix . 'books');
$chapters = $sm->introspectTable($prefix . 'chapters');
if ($pages->hasIndex('search')) {
Schema::table('pages', function (Blueprint $table) { Schema::table('pages', function (Blueprint $table) {
$table->dropIndex('search'); $table->dropIndex('search');
$table->dropIndex('name_search'); $table->dropIndex('name_search');
}); });
} }
if ($books->hasIndex('search')) { if (Schema::hasIndex('books', 'search')) {
Schema::table('books', function (Blueprint $table) { Schema::table('books', function (Blueprint $table) {
$table->dropIndex('search'); $table->dropIndex('search');
$table->dropIndex('name_search'); $table->dropIndex('name_search');
}); });
} }
if ($chapters->hasIndex('search')) { if (Schema::hasIndex('chapters', 'search')) {
Schema::table('chapters', function (Blueprint $table) { Schema::table('chapters', function (Blueprint $table) {
$table->dropIndex('search'); $table->dropIndex('search');
$table->dropIndex('name_search'); $table->dropIndex('name_search');

View File

@ -8,7 +8,7 @@ return new class extends Migration
/** /**
* Mapping of old polymorphic types to new simpler values. * Mapping of old polymorphic types to new simpler values.
*/ */
protected $changeMap = [ protected array $changeMap = [
'BookStack\\Bookshelf' => 'bookshelf', 'BookStack\\Bookshelf' => 'bookshelf',
'BookStack\\Book' => 'book', 'BookStack\\Book' => 'book',
'BookStack\\Chapter' => 'chapter', 'BookStack\\Chapter' => 'chapter',
@ -18,7 +18,7 @@ return new class extends Migration
/** /**
* Mapping of tables and columns that contain polymorphic types. * Mapping of tables and columns that contain polymorphic types.
*/ */
protected $columnsByTable = [ protected array $columnsByTable = [
'activities' => 'entity_type', 'activities' => 'entity_type',
'comments' => 'entity_type', 'comments' => 'entity_type',
'deletions' => 'deletable_type', 'deletions' => 'deletable_type',

View File

@ -9,7 +9,9 @@ parameters:
# The level 8 is the highest level # The level 8 is the highest level
level: 1 level: 1
phpVersion: 80200 phpVersion:
min: 80200
max: 80400
bootstrapFiles: bootstrapFiles:
- bootstrap/phpstan.php - bootstrap/phpstan.php
@ -20,5 +22,3 @@ parameters:
excludePaths: excludePaths:
- ./Config/**/*.php - ./Config/**/*.php
- ./dev/**/*.php - ./dev/**/*.php
checkMissingIterableValueType: false

View File

@ -5,45 +5,16 @@ use Illuminate\Contracts\Http\Kernel;
define('LARAVEL_START', microtime(true)); define('LARAVEL_START', microtime(true));
/* // Determine if the application is in maintenance mode...
|--------------------------------------------------------------------------
| Check If The Application Is Under Maintenance
|--------------------------------------------------------------------------
|
| If the application is in maintenance / demo mode via the "down" command
| we will load this file so that any pre-rendered content can be shown
| instead of starting the framework, which could cause an exception.
|
*/
if (file_exists(__DIR__ . '/../storage/framework/maintenance.php')) { if (file_exists(__DIR__ . '/../storage/framework/maintenance.php')) {
require __DIR__ . '/../storage/framework/maintenance.php'; require __DIR__ . '/../storage/framework/maintenance.php';
} }
/* // Register the Composer autoloader...
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| this application. We just need to utilize it! We'll simply require it
| into the script here so we don't need to manually load our classes.
|
*/
require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request using
| the application's HTTP kernel. Then, we will send the response back
| to this client's browser, allowing them to enjoy our application.
|
*/
// Run the application
$app = require_once __DIR__ . '/../bootstrap/app.php'; $app = require_once __DIR__ . '/../bootstrap/app.php';
$app->alias('request', Request::class); $app->alias('request', Request::class);

View File

@ -300,7 +300,7 @@ class PageTest extends TestCase
]); ]);
$resp = $this->asAdmin()->get('/pages/recently-updated'); $resp = $this->asAdmin()->get('/pages/recently-updated');
$this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', 'Updated 1 second ago by ' . $user->name); $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', 'Updated 0 seconds ago by ' . $user->name);
} }
public function test_recently_updated_pages_view_shows_parent_chain() public function test_recently_updated_pages_view_shows_parent_chain()