Full Code of BookStackApp/BookStack for AI

development 0120b475eb18 cached
2270 files
11.4 MB
3.1M tokens
8480 symbols
1 requests
Copy disabled (too large) Download .txt
Showing preview only (12,409K chars total). Download the full file to get everything.
Repository: BookStackApp/BookStack
Branch: development
Commit: 0120b475eb18
Files: 2270
Total size: 11.4 MB

Directory structure:
gitextract_hlplnxq_/

├── .gitattributes
├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── api_request.yml
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── feature_request.yml
│   │   ├── language_request.yml
│   │   ├── support_request.yml
│   │   └── z_blank_request.yml
│   ├── SECURITY.md
│   ├── translators.txt
│   └── workflows/
│       ├── analyse-php.yml
│       ├── lint-js.yml
│       ├── lint-php.yml
│       ├── test-js.yml
│       ├── test-migrations.yml
│       └── test-php.yml
├── .gitignore
├── LICENSE
├── app/
│   ├── Access/
│   │   ├── Controllers/
│   │   │   ├── ConfirmEmailController.php
│   │   │   ├── ForgotPasswordController.php
│   │   │   ├── HandlesPartialLogins.php
│   │   │   ├── LoginController.php
│   │   │   ├── MfaBackupCodesController.php
│   │   │   ├── MfaController.php
│   │   │   ├── MfaTotpController.php
│   │   │   ├── OidcController.php
│   │   │   ├── RegisterController.php
│   │   │   ├── ResetPasswordController.php
│   │   │   ├── Saml2Controller.php
│   │   │   ├── SocialController.php
│   │   │   ├── ThrottlesLogins.php
│   │   │   └── UserInviteController.php
│   │   ├── EmailConfirmationService.php
│   │   ├── ExternalBaseUserProvider.php
│   │   ├── GroupSyncService.php
│   │   ├── Guards/
│   │   │   ├── AsyncExternalBaseSessionGuard.php
│   │   │   ├── ExternalBaseSessionGuard.php
│   │   │   └── LdapSessionGuard.php
│   │   ├── Ldap.php
│   │   ├── LdapService.php
│   │   ├── LoginService.php
│   │   ├── Mfa/
│   │   │   ├── BackupCodeService.php
│   │   │   ├── MfaSession.php
│   │   │   ├── MfaValue.php
│   │   │   ├── TotpService.php
│   │   │   └── TotpValidationRule.php
│   │   ├── Notifications/
│   │   │   ├── ConfirmEmailNotification.php
│   │   │   ├── ResetPasswordNotification.php
│   │   │   └── UserInviteNotification.php
│   │   ├── Oidc/
│   │   │   ├── OidcAccessToken.php
│   │   │   ├── OidcException.php
│   │   │   ├── OidcIdToken.php
│   │   │   ├── OidcInvalidKeyException.php
│   │   │   ├── OidcInvalidTokenException.php
│   │   │   ├── OidcIssuerDiscoveryException.php
│   │   │   ├── OidcJwtSigningKey.php
│   │   │   ├── OidcJwtWithClaims.php
│   │   │   ├── OidcOAuthProvider.php
│   │   │   ├── OidcProviderSettings.php
│   │   │   ├── OidcService.php
│   │   │   ├── OidcUserDetails.php
│   │   │   ├── OidcUserinfoResponse.php
│   │   │   └── ProvidesClaims.php
│   │   ├── RegistrationService.php
│   │   ├── Saml2Service.php
│   │   ├── SocialAccount.php
│   │   ├── SocialAuthService.php
│   │   ├── SocialDriverManager.php
│   │   ├── UserInviteException.php
│   │   ├── UserInviteService.php
│   │   └── UserTokenService.php
│   ├── Activity/
│   │   ├── ActivityQueries.php
│   │   ├── ActivityType.php
│   │   ├── CommentRepo.php
│   │   ├── Controllers/
│   │   │   ├── AuditLogApiController.php
│   │   │   ├── AuditLogController.php
│   │   │   ├── CommentApiController.php
│   │   │   ├── CommentController.php
│   │   │   ├── FavouriteController.php
│   │   │   ├── TagController.php
│   │   │   ├── WatchController.php
│   │   │   └── WebhookController.php
│   │   ├── DispatchWebhookJob.php
│   │   ├── Models/
│   │   │   ├── Activity.php
│   │   │   ├── Comment.php
│   │   │   ├── Favouritable.php
│   │   │   ├── Favourite.php
│   │   │   ├── Loggable.php
│   │   │   ├── MentionHistory.php
│   │   │   ├── Tag.php
│   │   │   ├── View.php
│   │   │   ├── Viewable.php
│   │   │   ├── Watch.php
│   │   │   ├── Webhook.php
│   │   │   └── WebhookTrackedEvent.php
│   │   ├── Notifications/
│   │   │   ├── Handlers/
│   │   │   │   ├── BaseNotificationHandler.php
│   │   │   │   ├── CommentCreationNotificationHandler.php
│   │   │   │   ├── CommentMentionNotificationHandler.php
│   │   │   │   ├── NotificationHandler.php
│   │   │   │   ├── PageCreationNotificationHandler.php
│   │   │   │   └── PageUpdateNotificationHandler.php
│   │   │   ├── MessageParts/
│   │   │   │   ├── EntityLinkMessageLine.php
│   │   │   │   ├── EntityPathMessageLine.php
│   │   │   │   ├── LinkedMailMessageLine.php
│   │   │   │   └── ListMessageLine.php
│   │   │   ├── Messages/
│   │   │   │   ├── BaseActivityNotification.php
│   │   │   │   ├── CommentCreationNotification.php
│   │   │   │   ├── CommentMentionNotification.php
│   │   │   │   ├── PageCreationNotification.php
│   │   │   │   └── PageUpdateNotification.php
│   │   │   └── NotificationManager.php
│   │   ├── Queries/
│   │   │   └── WebhooksAllPaginatedAndSorted.php
│   │   ├── TagRepo.php
│   │   ├── Tools/
│   │   │   ├── ActivityLogger.php
│   │   │   ├── CommentTree.php
│   │   │   ├── CommentTreeNode.php
│   │   │   ├── EntityWatchers.php
│   │   │   ├── IpFormatter.php
│   │   │   ├── MentionParser.php
│   │   │   ├── TagClassGenerator.php
│   │   │   ├── UserEntityWatchOptions.php
│   │   │   ├── WatchedParentDetails.php
│   │   │   └── WebhookFormatter.php
│   │   └── WatchLevels.php
│   ├── Api/
│   │   ├── ApiDocsController.php
│   │   ├── ApiDocsGenerator.php
│   │   ├── ApiEntityListFormatter.php
│   │   ├── ApiToken.php
│   │   ├── ApiTokenGuard.php
│   │   ├── ListingResponseBuilder.php
│   │   └── UserApiTokenController.php
│   ├── App/
│   │   ├── AppVersion.php
│   │   ├── Application.php
│   │   ├── HomeController.php
│   │   ├── MailNotification.php
│   │   ├── MetaController.php
│   │   ├── Model.php
│   │   ├── Providers/
│   │   │   ├── AppServiceProvider.php
│   │   │   ├── AuthServiceProvider.php
│   │   │   ├── EventServiceProvider.php
│   │   │   ├── RouteServiceProvider.php
│   │   │   ├── ThemeServiceProvider.php
│   │   │   ├── TranslationServiceProvider.php
│   │   │   ├── ValidationRuleServiceProvider.php
│   │   │   └── ViewTweaksServiceProvider.php
│   │   ├── PwaManifestBuilder.php
│   │   ├── SluggableInterface.php
│   │   ├── SystemApiController.php
│   │   └── helpers.php
│   ├── Config/
│   │   ├── api.php
│   │   ├── app.php
│   │   ├── auth.php
│   │   ├── cache.php
│   │   ├── clockwork.php
│   │   ├── database.php
│   │   ├── debugbar.php
│   │   ├── exports.php
│   │   ├── filesystems.php
│   │   ├── hashing.php
│   │   ├── logging.php
│   │   ├── mail.php
│   │   ├── oidc.php
│   │   ├── queue.php
│   │   ├── saml2.php
│   │   ├── services.php
│   │   ├── session.php
│   │   ├── setting-defaults.php
│   │   └── view.php
│   ├── Console/
│   │   ├── Commands/
│   │   │   ├── AssignSortRuleCommand.php
│   │   │   ├── CleanupImagesCommand.php
│   │   │   ├── ClearActivityCommand.php
│   │   │   ├── ClearRevisionsCommand.php
│   │   │   ├── ClearViewsCommand.php
│   │   │   ├── CopyShelfPermissionsCommand.php
│   │   │   ├── CreateAdminCommand.php
│   │   │   ├── DeleteUsersCommand.php
│   │   │   ├── HandlesSingleUser.php
│   │   │   ├── InstallModuleCommand.php
│   │   │   ├── RefreshAvatarCommand.php
│   │   │   ├── RegeneratePermissionsCommand.php
│   │   │   ├── RegenerateReferencesCommand.php
│   │   │   ├── RegenerateSearchCommand.php
│   │   │   ├── ResetMfaCommand.php
│   │   │   ├── UpdateUrlCommand.php
│   │   │   └── UpgradeDatabaseEncodingCommand.php
│   │   └── Kernel.php
│   ├── Entities/
│   │   ├── BreadcrumbsViewComposer.php
│   │   ├── Controllers/
│   │   │   ├── BookApiController.php
│   │   │   ├── BookController.php
│   │   │   ├── BookshelfApiController.php
│   │   │   ├── BookshelfController.php
│   │   │   ├── ChapterApiController.php
│   │   │   ├── ChapterController.php
│   │   │   ├── PageApiController.php
│   │   │   ├── PageController.php
│   │   │   ├── PageRevisionController.php
│   │   │   ├── PageTemplateController.php
│   │   │   ├── RecycleBinApiController.php
│   │   │   └── RecycleBinController.php
│   │   ├── EntityExistsRule.php
│   │   ├── EntityProvider.php
│   │   ├── Models/
│   │   │   ├── Book.php
│   │   │   ├── BookChild.php
│   │   │   ├── Bookshelf.php
│   │   │   ├── Chapter.php
│   │   │   ├── ContainerTrait.php
│   │   │   ├── DeletableInterface.php
│   │   │   ├── Deletion.php
│   │   │   ├── Entity.php
│   │   │   ├── EntityContainerData.php
│   │   │   ├── EntityPageData.php
│   │   │   ├── EntityQueryBuilder.php
│   │   │   ├── EntityScope.php
│   │   │   ├── EntityTable.php
│   │   │   ├── HasCoverInterface.php
│   │   │   ├── HasDefaultTemplateInterface.php
│   │   │   ├── HasDescriptionInterface.php
│   │   │   ├── Page.php
│   │   │   ├── PageRevision.php
│   │   │   └── SlugHistory.php
│   │   ├── Queries/
│   │   │   ├── BookQueries.php
│   │   │   ├── BookshelfQueries.php
│   │   │   ├── ChapterQueries.php
│   │   │   ├── EntityQueries.php
│   │   │   ├── PageQueries.php
│   │   │   ├── PageRevisionQueries.php
│   │   │   ├── ProvidesEntityQueries.php
│   │   │   ├── QueryPopular.php
│   │   │   ├── QueryRecentlyViewed.php
│   │   │   └── QueryTopFavourites.php
│   │   ├── Repos/
│   │   │   ├── BaseRepo.php
│   │   │   ├── BookRepo.php
│   │   │   ├── BookshelfRepo.php
│   │   │   ├── ChapterRepo.php
│   │   │   ├── DeletionRepo.php
│   │   │   ├── PageRepo.php
│   │   │   └── RevisionRepo.php
│   │   └── Tools/
│   │       ├── BookContents.php
│   │       ├── Cloner.php
│   │       ├── EntityCover.php
│   │       ├── EntityDefaultTemplate.php
│   │       ├── EntityHtmlDescription.php
│   │       ├── EntityHydrator.php
│   │       ├── HierarchyTransformer.php
│   │       ├── Markdown/
│   │       │   ├── CheckboxConverter.php
│   │       │   ├── CustomDivConverter.php
│   │       │   ├── CustomImageConverter.php
│   │       │   ├── CustomListItemRenderer.php
│   │       │   ├── CustomParagraphConverter.php
│   │       │   ├── CustomStrikeThroughExtension.php
│   │       │   ├── CustomStrikethroughRenderer.php
│   │       │   ├── HtmlToMarkdown.php
│   │       │   ├── MarkdownToHtml.php
│   │       │   └── SpacedTagFallbackConverter.php
│   │       ├── MixedEntityListLoader.php
│   │       ├── MixedEntityRequestHelper.php
│   │       ├── NextPreviousContentLocator.php
│   │       ├── PageContent.php
│   │       ├── PageEditActivity.php
│   │       ├── PageEditorData.php
│   │       ├── PageEditorType.php
│   │       ├── PageIncludeContent.php
│   │       ├── PageIncludeParser.php
│   │       ├── PageIncludeTag.php
│   │       ├── ParentChanger.php
│   │       ├── PermissionsUpdater.php
│   │       ├── ShelfContext.php
│   │       ├── SiblingFetcher.php
│   │       ├── SlugGenerator.php
│   │       ├── SlugHistory.php
│   │       └── TrashCan.php
│   ├── Exceptions/
│   │   ├── ApiAuthException.php
│   │   ├── BookStackExceptionHandlerPage.php
│   │   ├── ConfirmationEmailException.php
│   │   ├── FileUploadException.php
│   │   ├── Handler.php
│   │   ├── HttpFetchException.php
│   │   ├── ImageUploadException.php
│   │   ├── JsonDebugException.php
│   │   ├── LdapException.php
│   │   ├── LoginAttemptEmailNeededException.php
│   │   ├── LoginAttemptException.php
│   │   ├── LoginAttemptInvalidUserException.php
│   │   ├── MoveOperationException.php
│   │   ├── NotFoundException.php
│   │   ├── NotifyException.php
│   │   ├── PdfExportException.php
│   │   ├── PermissionsException.php
│   │   ├── PrettyException.php
│   │   ├── SamlException.php
│   │   ├── SocialDriverNotConfigured.php
│   │   ├── SocialSignInAccountNotUsed.php
│   │   ├── SocialSignInException.php
│   │   ├── StoppedAuthenticationException.php
│   │   ├── ThemeException.php
│   │   ├── UserRegistrationException.php
│   │   ├── UserTokenExpiredException.php
│   │   ├── UserTokenNotFoundException.php
│   │   ├── UserUpdateException.php
│   │   ├── ZipExportException.php
│   │   ├── ZipImportException.php
│   │   └── ZipValidationException.php
│   ├── Exports/
│   │   ├── Controllers/
│   │   │   ├── BookExportApiController.php
│   │   │   ├── BookExportController.php
│   │   │   ├── ChapterExportApiController.php
│   │   │   ├── ChapterExportController.php
│   │   │   ├── ImportApiController.php
│   │   │   ├── ImportController.php
│   │   │   ├── PageExportApiController.php
│   │   │   └── PageExportController.php
│   │   ├── ExportFormatter.php
│   │   ├── Import.php
│   │   ├── ImportRepo.php
│   │   ├── PdfGenerator.php
│   │   └── ZipExports/
│   │       ├── Models/
│   │       │   ├── ZipExportAttachment.php
│   │       │   ├── ZipExportBook.php
│   │       │   ├── ZipExportChapter.php
│   │       │   ├── ZipExportImage.php
│   │       │   ├── ZipExportModel.php
│   │       │   ├── ZipExportPage.php
│   │       │   └── ZipExportTag.php
│   │       ├── ZipExportBuilder.php
│   │       ├── ZipExportFiles.php
│   │       ├── ZipExportReader.php
│   │       ├── ZipExportReferences.php
│   │       ├── ZipExportValidator.php
│   │       ├── ZipFileReferenceRule.php
│   │       ├── ZipImportReferences.php
│   │       ├── ZipImportRunner.php
│   │       ├── ZipReferenceParser.php
│   │       ├── ZipUniqueIdRule.php
│   │       └── ZipValidationHelper.php
│   ├── Facades/
│   │   ├── Activity.php
│   │   └── Theme.php
│   ├── Http/
│   │   ├── ApiController.php
│   │   ├── Controller.php
│   │   ├── DownloadResponseFactory.php
│   │   ├── HttpClientHistory.php
│   │   ├── HttpRequestService.php
│   │   ├── Kernel.php
│   │   ├── Middleware/
│   │   │   ├── ApiAuthenticate.php
│   │   │   ├── ApplyCspRules.php
│   │   │   ├── Authenticate.php
│   │   │   ├── AuthenticatedOrPendingMfa.php
│   │   │   ├── CheckEmailConfirmed.php
│   │   │   ├── CheckGuard.php
│   │   │   ├── CheckUserHasPermission.php
│   │   │   ├── EncryptCookies.php
│   │   │   ├── Localization.php
│   │   │   ├── PreventRequestsDuringMaintenance.php
│   │   │   ├── PreventResponseCaching.php
│   │   │   ├── RedirectIfAuthenticated.php
│   │   │   ├── RunThemeActions.php
│   │   │   ├── StartSessionExtended.php
│   │   │   ├── StartSessionIfCookieExists.php
│   │   │   ├── ThrottleApiRequests.php
│   │   │   ├── TrimStrings.php
│   │   │   ├── TrustHosts.php
│   │   │   ├── TrustProxies.php
│   │   │   └── VerifyCsrfToken.php
│   │   ├── RangeSupportedStream.php
│   │   └── Request.php
│   ├── Permissions/
│   │   ├── ContentPermissionApiController.php
│   │   ├── EntityPermissionEvaluator.php
│   │   ├── JointPermissionBuilder.php
│   │   ├── MassEntityPermissionEvaluator.php
│   │   ├── Models/
│   │   │   ├── EntityPermission.php
│   │   │   ├── JointPermission.php
│   │   │   └── RolePermission.php
│   │   ├── Permission.php
│   │   ├── PermissionApplicator.php
│   │   ├── PermissionFormData.php
│   │   ├── PermissionStatus.php
│   │   ├── PermissionsController.php
│   │   ├── PermissionsRepo.php
│   │   └── SimpleEntityData.php
│   ├── References/
│   │   ├── CrossLinkParser.php
│   │   ├── ModelResolvers/
│   │   │   ├── AttachmentModelResolver.php
│   │   │   ├── BookLinkModelResolver.php
│   │   │   ├── BookshelfLinkModelResolver.php
│   │   │   ├── ChapterLinkModelResolver.php
│   │   │   ├── CrossLinkModelResolver.php
│   │   │   ├── ImageModelResolver.php
│   │   │   ├── PageLinkModelResolver.php
│   │   │   └── PagePermalinkModelResolver.php
│   │   ├── Reference.php
│   │   ├── ReferenceChangeContext.php
│   │   ├── ReferenceController.php
│   │   ├── ReferenceFetcher.php
│   │   ├── ReferenceStore.php
│   │   └── ReferenceUpdater.php
│   ├── Search/
│   │   ├── Options/
│   │   │   ├── ExactSearchOption.php
│   │   │   ├── FilterSearchOption.php
│   │   │   ├── SearchOption.php
│   │   │   ├── TagSearchOption.php
│   │   │   └── TermSearchOption.php
│   │   ├── SearchApiController.php
│   │   ├── SearchController.php
│   │   ├── SearchIndex.php
│   │   ├── SearchOptionSet.php
│   │   ├── SearchOptions.php
│   │   ├── SearchResultsFormatter.php
│   │   ├── SearchRunner.php
│   │   ├── SearchTerm.php
│   │   └── SearchTextTokenizer.php
│   ├── Settings/
│   │   ├── AppSettingsStore.php
│   │   ├── MaintenanceController.php
│   │   ├── Setting.php
│   │   ├── SettingController.php
│   │   ├── SettingService.php
│   │   ├── StatusController.php
│   │   ├── TestEmailNotification.php
│   │   ├── UserNotificationPreferences.php
│   │   └── UserShortcutMap.php
│   ├── Sorting/
│   │   ├── BookSortController.php
│   │   ├── BookSortMap.php
│   │   ├── BookSortMapItem.php
│   │   ├── BookSorter.php
│   │   ├── SortRule.php
│   │   ├── SortRuleController.php
│   │   ├── SortRuleOperation.php
│   │   ├── SortSetOperationComparisons.php
│   │   └── SortUrl.php
│   ├── Theming/
│   │   ├── CustomHtmlHeadContentProvider.php
│   │   ├── ThemeController.php
│   │   ├── ThemeEvents.php
│   │   ├── ThemeModule.php
│   │   ├── ThemeModuleException.php
│   │   ├── ThemeModuleManager.php
│   │   ├── ThemeModuleZip.php
│   │   ├── ThemeService.php
│   │   └── ThemeViews.php
│   ├── Translation/
│   │   ├── FileLoader.php
│   │   ├── LocaleDefinition.php
│   │   ├── LocaleManager.php
│   │   └── MessageSelector.php
│   ├── Uploads/
│   │   ├── Attachment.php
│   │   ├── AttachmentService.php
│   │   ├── Controllers/
│   │   │   ├── AttachmentApiController.php
│   │   │   ├── AttachmentController.php
│   │   │   ├── DrawioImageController.php
│   │   │   ├── GalleryImageController.php
│   │   │   ├── ImageController.php
│   │   │   └── ImageGalleryApiController.php
│   │   ├── FaviconHandler.php
│   │   ├── FileStorage.php
│   │   ├── Image.php
│   │   ├── ImageRepo.php
│   │   ├── ImageResizer.php
│   │   ├── ImageService.php
│   │   ├── ImageStorage.php
│   │   ├── ImageStorageDisk.php
│   │   └── UserAvatars.php
│   ├── Users/
│   │   ├── Controllers/
│   │   │   ├── RoleApiController.php
│   │   │   ├── RoleController.php
│   │   │   ├── UserAccountController.php
│   │   │   ├── UserApiController.php
│   │   │   ├── UserController.php
│   │   │   ├── UserPreferencesController.php
│   │   │   ├── UserProfileController.php
│   │   │   └── UserSearchController.php
│   │   ├── Models/
│   │   │   ├── HasCreatorAndUpdater.php
│   │   │   ├── OwnableInterface.php
│   │   │   ├── Role.php
│   │   │   └── User.php
│   │   ├── Queries/
│   │   │   ├── RolesAllPaginatedAndSorted.php
│   │   │   ├── UserContentCounts.php
│   │   │   ├── UserRecentlyCreatedContent.php
│   │   │   └── UsersAllPaginatedAndSorted.php
│   │   └── UserRepo.php
│   └── Util/
│       ├── ConfiguredHtmlPurifier.php
│       ├── CspService.php
│       ├── DatabaseTransaction.php
│       ├── DateFormatter.php
│       ├── FilePathNormalizer.php
│       ├── HtmlContentFilter.php
│       ├── HtmlContentFilterConfig.php
│       ├── HtmlDescriptionFilter.php
│       ├── HtmlDocument.php
│       ├── HtmlNonceApplicator.php
│       ├── OutOfMemoryHandler.php
│       ├── SimpleListOptions.php
│       ├── SsrUrlValidator.php
│       ├── SvgIcon.php
│       └── WebSafeMimeSniffer.php
├── artisan
├── bookstack-system-cli
├── bootstrap/
│   ├── app.php
│   ├── cache/
│   │   └── .gitignore
│   └── phpstan.php
├── composer.json
├── crowdin.yml
├── database/
│   ├── .gitignore
│   ├── factories/
│   │   ├── Access/
│   │   │   ├── Mfa/
│   │   │   │   └── MfaValueFactory.php
│   │   │   └── SocialAccountFactory.php
│   │   ├── Activity/
│   │   │   └── Models/
│   │   │       ├── ActivityFactory.php
│   │   │       ├── CommentFactory.php
│   │   │       ├── FavouriteFactory.php
│   │   │       ├── TagFactory.php
│   │   │       ├── WatchFactory.php
│   │   │       ├── WebhookFactory.php
│   │   │       └── WebhookTrackedEventFactory.php
│   │   ├── Api/
│   │   │   └── ApiTokenFactory.php
│   │   ├── Entities/
│   │   │   └── Models/
│   │   │       ├── BookFactory.php
│   │   │       ├── BookshelfFactory.php
│   │   │       ├── ChapterFactory.php
│   │   │       ├── DeletionFactory.php
│   │   │       ├── PageFactory.php
│   │   │       ├── PageRevisionFactory.php
│   │   │       └── SlugHistoryFactory.php
│   │   ├── Exports/
│   │   │   └── ImportFactory.php
│   │   ├── Sorting/
│   │   │   └── SortRuleFactory.php
│   │   ├── Uploads/
│   │   │   ├── AttachmentFactory.php
│   │   │   └── ImageFactory.php
│   │   └── Users/
│   │       └── Models/
│   │           ├── RoleFactory.php
│   │           └── UserFactory.php
│   ├── migrations/
│   │   ├── .gitkeep
│   │   ├── 2014_10_12_000000_create_users_table.php
│   │   ├── 2014_10_12_100000_create_password_resets_table.php
│   │   ├── 2015_07_12_114933_create_books_table.php
│   │   ├── 2015_07_12_190027_create_pages_table.php
│   │   ├── 2015_07_13_172121_create_images_table.php
│   │   ├── 2015_07_27_172342_create_chapters_table.php
│   │   ├── 2015_08_08_200447_add_users_to_entities.php
│   │   ├── 2015_08_09_093534_create_page_revisions_table.php
│   │   ├── 2015_08_16_142133_create_activities_table.php
│   │   ├── 2015_08_29_105422_add_roles_and_permissions.php
│   │   ├── 2015_08_30_125859_create_settings_table.php
│   │   ├── 2015_08_31_175240_add_search_indexes.php
│   │   ├── 2015_09_04_165821_create_social_accounts_table.php
│   │   ├── 2015_09_05_164707_add_email_confirmation_table.php
│   │   ├── 2015_11_21_145609_create_views_table.php
│   │   ├── 2015_11_26_221857_add_entity_indexes.php
│   │   ├── 2015_12_05_145049_fulltext_weighting.php
│   │   ├── 2015_12_07_195238_add_image_upload_types.php
│   │   ├── 2015_12_09_195748_add_user_avatars.php
│   │   ├── 2016_01_11_210908_add_external_auth_to_users.php
│   │   ├── 2016_02_25_184030_add_slug_to_revisions.php
│   │   ├── 2016_02_27_120329_update_permissions_and_roles.php
│   │   ├── 2016_02_28_084200_add_entity_access_controls.php
│   │   ├── 2016_03_09_203143_add_page_revision_types.php
│   │   ├── 2016_03_13_082138_add_page_drafts.php
│   │   ├── 2016_03_25_123157_add_markdown_support.php
│   │   ├── 2016_04_09_100730_add_view_permissions_to_roles.php
│   │   ├── 2016_04_20_192649_create_joint_permissions_table.php
│   │   ├── 2016_05_06_185215_create_tags_table.php
│   │   ├── 2016_07_07_181521_add_summary_to_page_revisions.php
│   │   ├── 2016_09_29_101449_remove_hidden_roles.php
│   │   ├── 2016_10_09_142037_create_attachments_table.php
│   │   ├── 2017_01_21_163556_create_cache_table.php
│   │   ├── 2017_01_21_163602_create_sessions_table.php
│   │   ├── 2017_03_19_091553_create_search_index_table.php
│   │   ├── 2017_04_20_185112_add_revision_counts.php
│   │   ├── 2017_07_02_152834_update_db_encoding_to_ut8mb4.php
│   │   ├── 2017_08_01_130541_create_comments_table.php
│   │   ├── 2017_08_29_102650_add_cover_image_display.php
│   │   ├── 2018_07_15_173514_add_role_external_auth_id.php
│   │   ├── 2018_08_04_115700_create_bookshelves_table.php
│   │   ├── 2019_07_07_112515_add_template_support.php
│   │   ├── 2019_08_17_140214_add_user_invites_table.php
│   │   ├── 2019_12_29_120917_add_api_auth.php
│   │   ├── 2020_08_04_111754_drop_joint_permissions_id.php
│   │   ├── 2020_08_04_131052_remove_role_name_field.php
│   │   ├── 2020_09_19_094251_add_activity_indexes.php
│   │   ├── 2020_09_27_210059_add_entity_soft_deletes.php
│   │   ├── 2020_09_27_210528_create_deletions_table.php
│   │   ├── 2020_11_07_232321_simplify_activities_table.php
│   │   ├── 2020_12_30_173528_add_owned_by_field_to_entities.php
│   │   ├── 2021_01_30_225441_add_settings_type_column.php
│   │   ├── 2021_03_08_215138_add_user_slug.php
│   │   ├── 2021_05_15_173110_create_favourites_table.php
│   │   ├── 2021_06_30_173111_create_mfa_values_table.php
│   │   ├── 2021_07_03_085038_add_mfa_enforced_to_roles_table.php
│   │   ├── 2021_08_28_161743_add_export_role_permission.php
│   │   ├── 2021_09_26_044614_add_activities_ip_column.php
│   │   ├── 2021_11_26_070438_add_index_for_user_ip.php
│   │   ├── 2021_12_07_111343_create_webhooks_table.php
│   │   ├── 2021_12_13_152024_create_jobs_table.php
│   │   ├── 2021_12_13_152120_create_failed_jobs_table.php
│   │   ├── 2022_01_03_154041_add_webhooks_timeout_error_columns.php
│   │   ├── 2022_04_17_101741_add_editor_change_field_and_permission.php
│   │   ├── 2022_04_25_140741_update_polymorphic_types.php
│   │   ├── 2022_07_16_170051_drop_joint_permission_type.php
│   │   ├── 2022_08_17_092941_create_references_table.php
│   │   ├── 2022_09_02_082910_fix_shelf_cover_image_types.php
│   │   ├── 2022_10_07_091406_flatten_entity_permissions_table.php
│   │   ├── 2022_10_08_104202_drop_entity_restricted_field.php
│   │   ├── 2023_01_24_104625_refactor_joint_permissions_storage.php
│   │   ├── 2023_01_28_141230_copy_color_settings_for_dark_mode.php
│   │   ├── 2023_02_20_093655_increase_attachments_path_length.php
│   │   ├── 2023_02_23_200227_add_updated_at_index_to_pages.php
│   │   ├── 2023_06_10_071823_remove_guest_user_secondary_roles.php
│   │   ├── 2023_06_25_181952_remove_bookshelf_create_entity_permissions.php
│   │   ├── 2023_07_25_124945_add_receive_notifications_role_permissions.php
│   │   ├── 2023_07_31_104430_create_watches_table.php
│   │   ├── 2023_08_21_174248_increase_cache_size.php
│   │   ├── 2023_12_02_104541_add_default_template_to_books.php
│   │   ├── 2023_12_17_140913_add_description_html_to_entities.php
│   │   ├── 2024_01_01_104542_add_default_template_to_chapters.php
│   │   ├── 2024_02_04_141358_add_views_updated_index.php
│   │   ├── 2024_05_04_154409_rename_activity_relation_columns.php
│   │   ├── 2024_09_29_140340_ensure_editor_value_set.php
│   │   ├── 2024_10_29_114420_add_import_role_permission.php
│   │   ├── 2024_11_02_160700_create_imports_table.php
│   │   ├── 2024_11_27_171039_add_instance_id_setting.php
│   │   ├── 2025_01_29_180933_create_sort_rules_table.php
│   │   ├── 2025_02_05_150842_add_sort_rule_id_to_books.php
│   │   ├── 2025_04_18_215145_add_content_refs_and_archived_to_comments.php
│   │   ├── 2025_09_02_111542_remove_unused_columns.php
│   │   ├── 2025_09_15_132850_create_entities_table.php
│   │   ├── 2025_09_15_134701_migrate_entity_data.php
│   │   ├── 2025_09_15_134751_update_entity_relation_columns.php
│   │   ├── 2025_09_15_134813_drop_old_entity_tables.php
│   │   ├── 2025_10_18_163331_clean_user_id_references.php
│   │   ├── 2025_10_22_134507_update_comments_relation_field_names.php
│   │   ├── 2025_11_23_161812_create_slug_history_table.php
│   │   ├── 2025_12_15_140219_create_mention_history_table.php
│   │   └── 2025_12_19_103417_add_views_viewable_type_index.php
│   └── seeders/
│       ├── .gitkeep
│       ├── DatabaseSeeder.php
│       ├── DummyContentSeeder.php
│       └── LargeContentSeeder.php
├── dev/
│   ├── api/
│   │   ├── requests/
│   │   │   ├── attachments-create.json
│   │   │   ├── attachments-update.json
│   │   │   ├── books-create.json
│   │   │   ├── books-update.json
│   │   │   ├── chapters-create.json
│   │   │   ├── chapters-update.json
│   │   │   ├── comments-create.json
│   │   │   ├── comments-update.json
│   │   │   ├── content-permissions-update.json
│   │   │   ├── image-gallery-readDataForUrl.http
│   │   │   ├── image-gallery-update.json
│   │   │   ├── imports-run.json
│   │   │   ├── pages-create.json
│   │   │   ├── pages-update.json
│   │   │   ├── roles-create.json
│   │   │   ├── roles-update.json
│   │   │   ├── search-all.http
│   │   │   ├── shelves-create.json
│   │   │   ├── shelves-update.json
│   │   │   ├── users-create.json
│   │   │   ├── users-delete.json
│   │   │   └── users-update.json
│   │   └── responses/
│   │       ├── attachments-create.json
│   │       ├── attachments-list.json
│   │       ├── attachments-read.json
│   │       ├── attachments-update.json
│   │       ├── audit-log-list.json
│   │       ├── books-create.json
│   │       ├── books-list.json
│   │       ├── books-read.json
│   │       ├── books-update.json
│   │       ├── chapters-create.json
│   │       ├── chapters-list.json
│   │       ├── chapters-read.json
│   │       ├── chapters-update.json
│   │       ├── comments-create.json
│   │       ├── comments-list.json
│   │       ├── comments-read.json
│   │       ├── comments-update.json
│   │       ├── content-permissions-read.json
│   │       ├── content-permissions-update.json
│   │       ├── image-gallery-create.json
│   │       ├── image-gallery-list.json
│   │       ├── image-gallery-read.json
│   │       ├── image-gallery-update.json
│   │       ├── imports-create.json
│   │       ├── imports-list.json
│   │       ├── imports-read.json
│   │       ├── imports-run.json
│   │       ├── pages-create.json
│   │       ├── pages-list.json
│   │       ├── pages-read.json
│   │       ├── pages-update.json
│   │       ├── recycle-bin-destroy.json
│   │       ├── recycle-bin-list.json
│   │       ├── recycle-bin-restore.json
│   │       ├── roles-create.json
│   │       ├── roles-list.json
│   │       ├── roles-read.json
│   │       ├── roles-update.json
│   │       ├── search-all.json
│   │       ├── shelves-create.json
│   │       ├── shelves-list.json
│   │       ├── shelves-read.json
│   │       ├── shelves-update.json
│   │       ├── system-read.json
│   │       ├── users-create.json
│   │       ├── users-list.json
│   │       ├── users-read.json
│   │       └── users-update.json
│   ├── build/
│   │   ├── esbuild.mjs
│   │   ├── livereload.js
│   │   └── svg-blank-transform.js
│   ├── checksums/
│   │   ├── .gitignore
│   │   └── vendor
│   ├── docker/
│   │   ├── Dockerfile
│   │   ├── db-testing/
│   │   │   ├── Dockerfile
│   │   │   ├── readme.md
│   │   │   └── run.sh
│   │   ├── entrypoint.app.sh
│   │   ├── entrypoint.node.sh
│   │   └── php/
│   │       └── conf.d/
│   │           └── xdebug.ini
│   ├── docs/
│   │   ├── development.md
│   │   ├── javascript-code.md
│   │   ├── javascript-public-events.md
│   │   ├── logical-theme-system.md
│   │   ├── permission-scenario-testing.md
│   │   ├── php-testing.md
│   │   ├── portable-zip-file-format.md
│   │   ├── release-process.md
│   │   ├── theme-system-modules.md
│   │   ├── visual-theme-system.md
│   │   └── wysiwyg-js-api.md
│   └── licensing/
│       ├── gen-js-licenses
│       ├── gen-licenses-shared.php
│       ├── gen-php-licenses
│       ├── js-library-licenses.txt
│       └── php-library-licenses.txt
├── docker-compose.yml
├── eslint.config.mjs
├── jest.config.ts
├── lang/
│   ├── ar/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── bg/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── bn/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── bs/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ca/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── cs/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── cy/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── da/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── de/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── de_informal/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── el/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── en/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── es/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── es_AR/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── et/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── eu/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── fa/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── fi/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── fr/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── he/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── hr/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── hu/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── id/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── is/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── it/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ja/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ka/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ko/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ku/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── lt/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── lv/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── nb/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ne/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── nl/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── nn/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── pl/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── pt/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── pt_BR/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ro/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ru/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── sk/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── sl/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── sq/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── sr/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── sv/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── tk/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── tr/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── uk/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── uz/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── vi/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── zh_CN/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   └── zh_TW/
│       ├── activities.php
│       ├── auth.php
│       ├── common.php
│       ├── components.php
│       ├── editor.php
│       ├── entities.php
│       ├── errors.php
│       ├── notifications.php
│       ├── pagination.php
│       ├── passwords.php
│       ├── preferences.php
│       ├── settings.php
│       └── validation.php
├── package.json
├── phpcs.xml
├── phpstan.neon.dist
├── phpunit.xml
├── public/
│   ├── .htaccess
│   ├── index.php
│   ├── libs/
│   │   └── tinymce/
│   │       ├── langs/
│   │       │   └── README.md
│   │       ├── license.txt
│   │       ├── skins/
│   │       │   ├── content/
│   │       │   │   ├── dark/
│   │       │   │   │   └── content.js
│   │       │   │   ├── default/
│   │       │   │   │   └── content.js
│   │       │   │   ├── document/
│   │       │   │   │   └── content.js
│   │       │   │   ├── tinymce-5/
│   │       │   │   │   └── content.js
│   │       │   │   ├── tinymce-5-dark/
│   │       │   │   │   └── content.js
│   │       │   │   └── writer/
│   │       │   │       └── content.js
│   │       │   └── ui/
│   │       │       ├── tinymce-5/
│   │       │       │   ├── content.inline.js
│   │       │       │   ├── content.js
│   │       │       │   ├── skin.js
│   │       │       │   └── skin.shadowdom.js
│   │       │       └── tinymce-5-dark/
│   │       │           ├── content.inline.js
│   │       │           ├── content.js
│   │       │           ├── skin.js
│   │       │           └── skin.shadowdom.js
│   │       └── tinymce.d.ts
│   ├── uploads/
│   │   ├── .gitignore
│   │   └── .htaccess
│   └── web.config
├── readme.md
├── resources/
│   ├── js/
│   │   ├── app.ts
│   │   ├── code/
│   │   │   ├── index.mjs
│   │   │   ├── languages.js
│   │   │   ├── legacy-modes.mjs
│   │   │   ├── setups.js
│   │   │   ├── simple-editor-interface.js
│   │   │   ├── themes.js
│   │   │   └── views.js
│   │   ├── components/
│   │   │   ├── add-remove-rows.js
│   │   │   ├── ajax-delete-row.ts
│   │   │   ├── ajax-form.js
│   │   │   ├── api-nav.ts
│   │   │   ├── attachments-list.js
│   │   │   ├── attachments.js
│   │   │   ├── auto-submit.js
│   │   │   ├── auto-suggest.js
│   │   │   ├── back-to-top.js
│   │   │   ├── book-sort.js
│   │   │   ├── chapter-contents.js
│   │   │   ├── code-editor.js
│   │   │   ├── code-highlighter.js
│   │   │   ├── code-textarea.js
│   │   │   ├── collapsible.js
│   │   │   ├── component.js
│   │   │   ├── confirm-dialog.js
│   │   │   ├── custom-checkbox.js
│   │   │   ├── details-highlighter.js
│   │   │   ├── dropdown-search.js
│   │   │   ├── dropdown.js
│   │   │   ├── dropzone.js
│   │   │   ├── editor-toolbox.ts
│   │   │   ├── entity-permissions.js
│   │   │   ├── entity-search.js
│   │   │   ├── entity-selector-popup.ts
│   │   │   ├── entity-selector.ts
│   │   │   ├── event-emit-select.js
│   │   │   ├── expand-toggle.js
│   │   │   ├── global-search.js
│   │   │   ├── header-mobile-toggle.js
│   │   │   ├── image-manager.js
│   │   │   ├── image-picker.js
│   │   │   ├── index.ts
│   │   │   ├── list-sort-control.js
│   │   │   ├── loading-button.ts
│   │   │   ├── markdown-editor.js
│   │   │   ├── new-user-password.js
│   │   │   ├── notification.js
│   │   │   ├── optional-input.js
│   │   │   ├── page-comment-reference.ts
│   │   │   ├── page-comment.ts
│   │   │   ├── page-comments.ts
│   │   │   ├── page-display.js
│   │   │   ├── page-editor.js
│   │   │   ├── page-picker.js
│   │   │   ├── permissions-table.js
│   │   │   ├── pointer.ts
│   │   │   ├── popup.js
│   │   │   ├── setting-app-color-scheme.js
│   │   │   ├── setting-color-picker.js
│   │   │   ├── setting-homepage-control.js
│   │   │   ├── shelf-sort.js
│   │   │   ├── shortcut-input.js
│   │   │   ├── shortcuts.js
│   │   │   ├── sort-rule-manager.ts
│   │   │   ├── sortable-list.js
│   │   │   ├── submit-on-change.js
│   │   │   ├── tabs.ts
│   │   │   ├── tag-manager.js
│   │   │   ├── template-manager.js
│   │   │   ├── toggle-switch.js
│   │   │   ├── tri-layout.ts
│   │   │   ├── user-select.js
│   │   │   ├── webhook-events.js
│   │   │   ├── wysiwyg-editor-tinymce.js
│   │   │   ├── wysiwyg-editor.js
│   │   │   └── wysiwyg-input.ts
│   │   ├── custom.d.ts
│   │   ├── global.d.ts
│   │   ├── markdown/
│   │   │   ├── actions.ts
│   │   │   ├── codemirror.ts
│   │   │   ├── common-events.ts
│   │   │   ├── display.ts
│   │   │   ├── dom-handlers.ts
│   │   │   ├── index.mts
│   │   │   ├── inputs/
│   │   │   │   ├── codemirror.ts
│   │   │   │   ├── interface.ts
│   │   │   │   └── textarea.ts
│   │   │   ├── markdown.ts
│   │   │   ├── settings.ts
│   │   │   └── shortcuts.ts
│   │   ├── services/
│   │   │   ├── __tests__/
│   │   │   │   └── translations.test.ts
│   │   │   ├── animations.ts
│   │   │   ├── clipboard.ts
│   │   │   ├── components.ts
│   │   │   ├── dates.ts
│   │   │   ├── dom.ts
│   │   │   ├── drawio.ts
│   │   │   ├── dual-lists.ts
│   │   │   ├── events.ts
│   │   │   ├── http.ts
│   │   │   ├── keyboard-navigation.ts
│   │   │   ├── store.ts
│   │   │   ├── text.ts
│   │   │   ├── translations.ts
│   │   │   ├── util.ts
│   │   │   └── vdom.ts
│   │   ├── wysiwyg/
│   │   │   ├── api/
│   │   │   │   ├── __tests__/
│   │   │   │   │   ├── api-test-utils.ts
│   │   │   │   │   ├── content.test.ts
│   │   │   │   │   └── ui.test.ts
│   │   │   │   ├── api.ts
│   │   │   │   ├── content.ts
│   │   │   │   └── ui.ts
│   │   │   ├── index.ts
│   │   │   ├── lexical/
│   │   │   │   ├── ORIGINAL-LEXICAL-LICENSE
│   │   │   │   ├── clipboard/
│   │   │   │   │   ├── clipboard.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── core/
│   │   │   │   │   ├── LexicalCommands.ts
│   │   │   │   │   ├── LexicalConstants.ts
│   │   │   │   │   ├── LexicalEditor.ts
│   │   │   │   │   ├── LexicalEditorState.ts
│   │   │   │   │   ├── LexicalEvents.ts
│   │   │   │   │   ├── LexicalGC.ts
│   │   │   │   │   ├── LexicalMutations.ts
│   │   │   │   │   ├── LexicalNode.ts
│   │   │   │   │   ├── LexicalNormalization.ts
│   │   │   │   │   ├── LexicalReconciler.ts
│   │   │   │   │   ├── LexicalSelection.ts
│   │   │   │   │   ├── LexicalUpdates.ts
│   │   │   │   │   ├── LexicalUtils.ts
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   ├── unit/
│   │   │   │   │   │   │   ├── HTMLCopyAndPaste.test.ts
│   │   │   │   │   │   │   ├── LexicalEditor.test.ts
│   │   │   │   │   │   │   ├── LexicalEditorState.test.ts
│   │   │   │   │   │   │   ├── LexicalNode.test.ts
│   │   │   │   │   │   │   ├── LexicalNormalization.test.ts
│   │   │   │   │   │   │   ├── LexicalSelection.test.ts
│   │   │   │   │   │   │   ├── LexicalSerialization.test.ts
│   │   │   │   │   │   │   └── LexicalUtils.test.ts
│   │   │   │   │   │   └── utils/
│   │   │   │   │   │       └── index.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── nodes/
│   │   │   │   │   │   ├── ArtificialNode.ts
│   │   │   │   │   │   ├── CommonBlockNode.ts
│   │   │   │   │   │   ├── LexicalDecoratorNode.ts
│   │   │   │   │   │   ├── LexicalElementNode.ts
│   │   │   │   │   │   ├── LexicalLineBreakNode.ts
│   │   │   │   │   │   ├── LexicalParagraphNode.ts
│   │   │   │   │   │   ├── LexicalRootNode.ts
│   │   │   │   │   │   ├── LexicalTabNode.ts
│   │   │   │   │   │   ├── LexicalTextNode.ts
│   │   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   │   └── unit/
│   │   │   │   │   │   │       ├── LexicalElementNode.test.ts
│   │   │   │   │   │   │       ├── LexicalGC.test.ts
│   │   │   │   │   │   │       ├── LexicalLineBreakNode.test.ts
│   │   │   │   │   │   │       ├── LexicalParagraphNode.test.ts
│   │   │   │   │   │   │       ├── LexicalRootNode.test.ts
│   │   │   │   │   │   │       ├── LexicalTabNode.test.ts
│   │   │   │   │   │   │       └── LexicalTextNode.test.ts
│   │   │   │   │   │   └── common.ts
│   │   │   │   │   └── shared/
│   │   │   │   │       ├── __mocks__/
│   │   │   │   │       │   └── invariant.ts
│   │   │   │   │       ├── canUseDOM.ts
│   │   │   │   │       ├── caretFromPoint.ts
│   │   │   │   │       ├── environment.ts
│   │   │   │   │       ├── invariant.ts
│   │   │   │   │       ├── normalizeClassNames.ts
│   │   │   │   │       ├── simpleDiffWithCursor.ts
│   │   │   │   │       └── warnOnlyOnce.ts
│   │   │   │   ├── headless/
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       └── LexicalHeadlessEditor.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── history/
│   │   │   │   │   └── index.ts
│   │   │   │   ├── html/
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       └── LexicalHtml.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── link/
│   │   │   │   │   ├── LexicalMentionNode.ts
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       ├── LexicalAutoLinkNode.test.ts
│   │   │   │   │   │       └── LexicalLinkNode.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── list/
│   │   │   │   │   ├── LexicalListItemNode.ts
│   │   │   │   │   ├── LexicalListNode.ts
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       ├── LexicalListItemNode.test.ts
│   │   │   │   │   │       ├── LexicalListNode.test.ts
│   │   │   │   │   │       └── utils.test.ts
│   │   │   │   │   ├── formatList.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── readme.md
│   │   │   │   ├── rich-text/
│   │   │   │   │   ├── LexicalCalloutNode.ts
│   │   │   │   │   ├── LexicalCodeBlockNode.ts
│   │   │   │   │   ├── LexicalDetailsNode.ts
│   │   │   │   │   ├── LexicalDiagramNode.ts
│   │   │   │   │   ├── LexicalHeadingNode.ts
│   │   │   │   │   ├── LexicalHorizontalRuleNode.ts
│   │   │   │   │   ├── LexicalImageNode.ts
│   │   │   │   │   ├── LexicalMediaNode.ts
│   │   │   │   │   ├── LexicalQuoteNode.ts
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       ├── LexicalDetailsNode.test.ts
│   │   │   │   │   │       ├── LexicalHeadingNode.test.ts
│   │   │   │   │   │       ├── LexicalMediaNode.test.ts
│   │   │   │   │   │       └── LexicalQuoteNode.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── selection/
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   ├── unit/
│   │   │   │   │   │   │   ├── LexicalSelection.test.ts
│   │   │   │   │   │   │   └── LexicalSelectionHelpers.test.ts
│   │   │   │   │   │   └── utils/
│   │   │   │   │   │       └── index.ts
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── lexical-node.ts
│   │   │   │   │   ├── range-selection.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── table/
│   │   │   │   │   ├── LexicalCaptionNode.ts
│   │   │   │   │   ├── LexicalTableCellNode.ts
│   │   │   │   │   ├── LexicalTableCommands.ts
│   │   │   │   │   ├── LexicalTableNode.ts
│   │   │   │   │   ├── LexicalTableObserver.ts
│   │   │   │   │   ├── LexicalTableRowNode.ts
│   │   │   │   │   ├── LexicalTableSelection.ts
│   │   │   │   │   ├── LexicalTableSelectionHelpers.ts
│   │   │   │   │   ├── LexicalTableUtils.ts
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       ├── LexicalTableCellNode.test.ts
│   │   │   │   │   │       ├── LexicalTableNode.test.ts
│   │   │   │   │   │       ├── LexicalTableRowNode.test.ts
│   │   │   │   │   │       └── LexicalTableSelection.test.ts
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── utils/
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       ├── LexicalElementHelpers.test.ts
│   │   │   │   │   │       ├── LexicalEventHelpers.test.ts
│   │   │   │   │   │       ├── LexicalNodeHelpers.test.ts
│   │   │   │   │   │       ├── LexicalRootHelpers.test.ts
│   │   │   │   │   │       ├── LexicalUtilsKlassEqual.test.ts
│   │   │   │   │   │       ├── LexicalUtilsSplitNode.test.ts
│   │   │   │   │   │       ├── LexlcaiUtilsInsertNodeToNearestRoot.test.ts
│   │   │   │   │   │       └── mergeRegister.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── markSelection.ts
│   │   │   │   │   ├── mergeRegister.ts
│   │   │   │   │   ├── positionNodeOnRange.ts
│   │   │   │   │   └── px.ts
│   │   │   │   └── yjs/
│   │   │   │       ├── Bindings.ts
│   │   │   │       ├── CollabDecoratorNode.ts
│   │   │   │       ├── CollabElementNode.ts
│   │   │   │       ├── CollabLineBreakNode.ts
│   │   │   │       ├── CollabTextNode.ts
│   │   │   │       ├── SyncCursors.ts
│   │   │   │       ├── SyncEditorStates.ts
│   │   │   │       ├── Utils.ts
│   │   │   │       ├── index.ts
│   │   │   │       └── types.ts
│   │   │   ├── nodes.ts
│   │   │   ├── services/
│   │   │   │   ├── __tests__/
│   │   │   │   │   ├── auto-links.test.ts
│   │   │   │   │   ├── keyboard-handling.test.ts
│   │   │   │   │   └── mouse-handling.test.ts
│   │   │   │   ├── auto-links.ts
│   │   │   │   ├── common-events.ts
│   │   │   │   ├── drop-paste-handling.ts
│   │   │   │   ├── keyboard-handling.ts
│   │   │   │   ├── mentions.ts
│   │   │   │   ├── mouse-handling.ts
│   │   │   │   ├── selection-handling.ts
│   │   │   │   └── shortcuts.ts
│   │   │   ├── testing.md
│   │   │   ├── ui/
│   │   │   │   ├── decorators/
│   │   │   │   │   ├── CodeBlockDecorator.ts
│   │   │   │   │   ├── DiagramDecorator.ts
│   │   │   │   │   └── MentionDecorator.ts
│   │   │   │   ├── defaults/
│   │   │   │   │   ├── buttons/
│   │   │   │   │   │   ├── alignments.ts
│   │   │   │   │   │   ├── block-formats.ts
│   │   │   │   │   │   ├── controls.ts
│   │   │   │   │   │   ├── inline-formats.ts
│   │   │   │   │   │   ├── lists.ts
│   │   │   │   │   │   ├── objects.ts
│   │   │   │   │   │   └── tables.ts
│   │   │   │   │   ├── forms/
│   │   │   │   │   │   ├── controls.ts
│   │   │   │   │   │   ├── objects.ts
│   │   │   │   │   │   └── tables.ts
│   │   │   │   │   ├── modals.ts
│   │   │   │   │   └── toolbars.ts
│   │   │   │   ├── framework/
│   │   │   │   │   ├── blocks/
│   │   │   │   │   │   ├── action-field.ts
│   │   │   │   │   │   ├── button-with-menu.ts
│   │   │   │   │   │   ├── color-button.ts
│   │   │   │   │   │   ├── color-field.ts
│   │   │   │   │   │   ├── color-picker.ts
│   │   │   │   │   │   ├── dropdown-button.ts
│   │   │   │   │   │   ├── external-content.ts
│   │   │   │   │   │   ├── format-menu.ts
│   │   │   │   │   │   ├── format-preview-button.ts
│   │   │   │   │   │   ├── link-field.ts
│   │   │   │   │   │   ├── menu-button.ts
│   │   │   │   │   │   ├── overflow-container.ts
│   │   │   │   │   │   ├── separator.ts
│   │   │   │   │   │   └── table-creator.ts
│   │   │   │   │   ├── buttons.ts
│   │   │   │   │   ├── core.ts
│   │   │   │   │   ├── decorator.ts
│   │   │   │   │   ├── forms.ts
│   │   │   │   │   ├── helpers/
│   │   │   │   │   │   ├── dropdowns.ts
│   │   │   │   │   │   ├── mouse-drag-tracker.ts
│   │   │   │   │   │   ├── node-resizer.ts
│   │   │   │   │   │   ├── table-resizer.ts
│   │   │   │   │   │   ├── table-selection-handler.ts
│   │   │   │   │   │   └── task-list-handler.ts
│   │   │   │   │   ├── manager.ts
│   │   │   │   │   ├── modals.ts
│   │   │   │   │   └── toolbars.ts
│   │   │   │   └── index.ts
│   │   │   └── utils/
│   │   │       ├── __tests__/
│   │   │       │   └── lists.test.ts
│   │   │       ├── actions.ts
│   │   │       ├── details.ts
│   │   │       ├── diagrams.ts
│   │   │       ├── dom.ts
│   │   │       ├── formats.ts
│   │   │       ├── images.ts
│   │   │       ├── links.ts
│   │   │       ├── lists.ts
│   │   │       ├── node-clipboard.ts
│   │   │       ├── nodes.ts
│   │   │       ├── selection.ts
│   │   │       ├── table-copy-paste.ts
│   │   │       ├── table-map.ts
│   │   │       └── tables.ts
│   │   └── wysiwyg-tinymce/
│   │       ├── common-events.js
│   │       ├── config.js
│   │       ├── drop-paste-handling.js
│   │       ├── filters.js
│   │       ├── fixes.js
│   │       ├── icons.js
│   │       ├── plugin-codeeditor.js
│   │       ├── plugin-drawio.js
│   │       ├── plugins-about.js
│   │       ├── plugins-customhr.js
│   │       ├── plugins-details.js
│   │       ├── plugins-imagemanager.js
│   │       ├── plugins-stub.js
│   │       ├── plugins-table-additions.js
│   │       ├── plugins-tasklist.js
│   │       ├── scrolling.js
│   │       ├── shortcuts.js
│   │       ├── toolbars.js
│   │       └── util.js
│   ├── sass/
│   │   ├── _animations.scss
│   │   ├── _blocks.scss
│   │   ├── _buttons.scss
│   │   ├── _codemirror.scss
│   │   ├── _colors.scss
│   │   ├── _components.scss
│   │   ├── _content.scss
│   │   ├── _editor.scss
│   │   ├── _footer.scss
│   │   ├── _forms.scss
│   │   ├── _header.scss
│   │   ├── _html.scss
│   │   ├── _layout.scss
│   │   ├── _lists.scss
│   │   ├── _mixins.scss
│   │   ├── _opacity.scss
│   │   ├── _pages.scss
│   │   ├── _print.scss
│   │   ├── _reset.scss
│   │   ├── _spacing.scss
│   │   ├── _tables.scss
│   │   ├── _text.scss
│   │   ├── _tinymce.scss
│   │   ├── _vars.scss
│   │   ├── export-styles.scss
│   │   └── styles.scss
│   └── views/
│       ├── api-docs/
│       │   ├── index.blade.php
│       │   └── parts/
│       │       ├── endpoint.blade.php
│       │       └── getting-started.blade.php
│       ├── attachments/
│       │   ├── list.blade.php
│       │   ├── manager-edit-form.blade.php
│       │   ├── manager-link-form.blade.php
│       │   ├── manager-list.blade.php
│       │   └── manager.blade.php
│       ├── auth/
│       │   ├── invite-set-password.blade.php
│       │   ├── login-initiate.blade.php
│       │   ├── login.blade.php
│       │   ├── parts/
│       │   │   ├── login-form-ldap.blade.php
│       │   │   ├── login-form-oidc.blade.php
│       │   │   ├── login-form-saml2.blade.php
│       │   │   ├── login-form-standard.blade.php
│       │   │   ├── login-message.blade.php
│       │   │   └── register-message.blade.php
│       │   ├── passwords/
│       │   │   ├── email.blade.php
│       │   │   └── reset.blade.php
│       │   ├── register-confirm-accept.blade.php
│       │   ├── register-confirm-awaiting.blade.php
│       │   ├── register-confirm.blade.php
│       │   └── register.blade.php
│       ├── books/
│       │   ├── copy.blade.php
│       │   ├── create.blade.php
│       │   ├── delete.blade.php
│       │   ├── edit.blade.php
│       │   ├── index.blade.php
│       │   ├── parts/
│       │   │   ├── convert-to-shelf.blade.php
│       │   │   ├── form.blade.php
│       │   │   ├── index-sidebar-section-actions.blade.php
│       │   │   ├── index-sidebar-section-new.blade.php
│       │   │   ├── index-sidebar-section-popular.blade.php
│       │   │   ├── index-sidebar-section-recents.blade.php
│       │   │   ├── list-item.blade.php
│       │   │   ├── list.blade.php
│       │   │   ├── show-sidebar-section-actions.blade.php
│       │   │   ├── show-sidebar-section-activity.blade.php
│       │   │   ├── show-sidebar-section-details.blade.php
│       │   │   ├── show-sidebar-section-shelves.blade.php
│       │   │   ├── show-sidebar-section-tags.blade.php
│       │   │   ├── sort-box-actions.blade.php
│       │   │   └── sort-box.blade.php
│       │   ├── permissions.blade.php
│       │   ├── references.blade.php
│       │   ├── show.blade.php
│       │   └── sort.blade.php
│       ├── chapters/
│       │   ├── copy.blade.php
│       │   ├── create.blade.php
│       │   ├── delete.blade.php
│       │   ├── edit.blade.php
│       │   ├── move.blade.php
│       │   ├── parts/
│       │   │   ├── child-menu.blade.php
│       │   │   ├── convert-to-book.blade.php
│       │   │   ├── form.blade.php
│       │   │   ├── list-item.blade.php
│       │   │   ├── show-sidebar-section-actions.blade.php
│       │   │   ├── show-sidebar-section-details.blade.php
│       │   │   └── show-sidebar-section-tags.blade.php
│       │   ├── permissions.blade.php
│       │   ├── references.blade.php
│       │   └── show.blade.php
│       ├── comments/
│       │   ├── comment-branch.blade.php
│       │   ├── comment.blade.php
│       │   ├── comments.blade.php
│       │   └── create.blade.php
│       ├── common/
│       │   ├── activity-item.blade.php
│       │   ├── activity-list.blade.php
│       │   ├── confirm-dialog.blade.php
│       │   ├── dark-mode-toggle.blade.php
│       │   ├── detailed-listing-paginated.blade.php
│       │   ├── detailed-listing-with-more.blade.php
│       │   ├── loading-icon.blade.php
│       │   ├── sort.blade.php
│       │   └── status-indicator.blade.php
│       ├── entities/
│       │   ├── body-tag-classes.blade.php
│       │   ├── book-tree.blade.php
│       │   ├── breadcrumb-listing.blade.php
│       │   ├── breadcrumbs.blade.php
│       │   ├── copy-considerations.blade.php
│       │   ├── export-menu.blade.php
│       │   ├── favourite-action.blade.php
│       │   ├── grid-item.blade.php
│       │   ├── icon-link.blade.php
│       │   ├── list-basic.blade.php
│       │   ├── list-item-basic.blade.php
│       │   ├── list-item.blade.php
│       │   ├── list.blade.php
│       │   ├── meta.blade.php
│       │   ├── references.blade.php
│       │   ├── search-form.blade.php
│       │   ├── search-results.blade.php
│       │   ├── selector-popup.blade.php
│       │   ├── selector.blade.php
│       │   ├── sibling-navigation.blade.php
│       │   ├── tag-list.blade.php
│       │   ├── tag-manager-list.blade.php
│       │   ├── tag-manager.blade.php
│       │   ├── tag.blade.php
│       │   ├── template-selector.blade.php
│       │   ├── view-toggle.blade.php
│       │   ├── watch-action.blade.php
│       │   └── watch-controls.blade.php
│       ├── errors/
│       │   ├── 404.blade.php
│       │   ├── 500.blade.php
│       │   ├── 503.blade.php
│       │   ├── debug.blade.php
│       │   └── parts/
│       │       └── not-found-text.blade.php
│       ├── exports/
│       │   ├── book.blade.php
│       │   ├── chapter.blade.php
│       │   ├── import-show.blade.php
│       │   ├── import.blade.php
│       │   ├── page.blade.php
│       │   └── parts/
│       │       ├── book-contents-menu.blade.php
│       │       ├── chapter-contents-menu.blade.php
│       │       ├── chapter-item.blade.php
│       │       ├── custom-head.blade.php
│       │       ├── import-item.blade.php
│       │       ├── import.blade.php
│       │       ├── meta.blade.php
│       │       ├── page-item.blade.php
│       │       └── styles.blade.php
│       ├── form/
│       │   ├── checkbox.blade.php
│       │   ├── custom-checkbox.blade.php
│       │   ├── date.blade.php
│       │   ├── description-html-input.blade.php
│       │   ├── editor-translations.blade.php
│       │   ├── entity-permissions-row.blade.php
│       │   ├── entity-permissions.blade.php
│       │   ├── errors.blade.php
│       │   ├── image-picker.blade.php
│       │   ├── number.blade.php
│       │   ├── page-picker.blade.php
│       │   ├── password.blade.php
│       │   ├── request-query-inputs.blade.php
│       │   ├── role-checkboxes.blade.php
│       │   ├── role-select.blade.php
│       │   ├── simple-dropzone.blade.php
│       │   ├── text.blade.php
│       │   ├── textarea.blade.php
│       │   ├── toggle-switch.blade.php
│       │   ├── user-mention-list.blade.php
│       │   ├── user-select-list.blade.php
│       │   └── user-select.blade.php
│       ├── help/
│       │   ├── licenses.blade.php
│       │   ├── tinymce.blade.php
│       │   └── wysiwyg.blade.php
│       ├── home/
│       │   ├── books.blade.php
│       │   ├── default.blade.php
│       │   ├── parts/
│       │   │   ├── expand-toggle.blade.php
│       │   │   └── sidebar.blade.php
│       │   ├── shelves.blade.php
│       │   └── specific-page.blade.php
│       ├── layouts/
│       │   ├── base.blade.php
│       │   ├── export.blade.php
│       │   ├── parts/
│       │   │   ├── base-body-end.blade.php
│       │   │   ├── base-body-start.blade.php
│       │   │   ├── custom-head.blade.php
│       │   │   ├── custom-styles.blade.php
│       │   │   ├── export-body-end.blade.php
│       │   │   ├── export-body-start.blade.php
│       │   │   ├── footer.blade.php
│       │   │   ├── header-links-start.blade.php
│       │   │   ├── header-links.blade.php
│       │   │   ├── header-logo.blade.php
│       │   │   ├── header-search.blade.php
│       │   │   ├── header-user-menu.blade.php
│       │   │   ├── header.blade.php
│       │   │   ├── notifications.blade.php
│       │   │   └── skip-to-content.blade.php
│       │   ├── plain.blade.php
│       │   ├── simple.blade.php
│       │   └── tri.blade.php
│       ├── mfa/
│       │   ├── backup-codes-generate.blade.php
│       │   ├── parts/
│       │   │   ├── setup-method-row.blade.php
│       │   │   ├── verify-backup_codes.blade.php
│       │   │   └── verify-totp.blade.php
│       │   ├── setup.blade.php
│       │   ├── totp-generate.blade.php
│       │   └── verify.blade.php
│       ├── misc/
│       │   ├── opensearch.blade.php
│       │   └── robots.blade.php
│       ├── pages/
│       │   ├── copy.blade.php
│       │   ├── delete.blade.php
│       │   ├── edit.blade.php
│       │   ├── guest-create.blade.php
│       │   ├── move.blade.php
│       │   ├── parts/
│       │   │   ├── code-editor.blade.php
│       │   │   ├── editor-toolbar.blade.php
│       │   │   ├── editor-toolbox.blade.php
│       │   │   ├── form.blade.php
│       │   │   ├── image-manager-form.blade.php
│       │   │   ├── image-manager-list.blade.php
│       │   │   ├── image-manager.blade.php
│       │   │   ├── list-item.blade.php
│       │   │   ├── markdown-editor.blade.php
│       │   │   ├── page-display.blade.php
│       │   │   ├── pointer.blade.php
│       │   │   ├── revisions-index-row.blade.php
│       │   │   ├── show-sidebar-section-actions.blade.php
│       │   │   ├── show-sidebar-section-attachments.blade.php
│       │   │   ├── show-sidebar-section-details.blade.php
│       │   │   ├── show-sidebar-section-page-nav.blade.php
│       │   │   ├── show-sidebar-section-tags.blade.php
│       │   │   ├── template-manager-list.blade.php
│       │   │   ├── template-manager.blade.php
│       │   │   ├── toolbox-comments.blade.php
│       │   │   ├── wysiwyg-editor-tinymce.blade.php
│       │   │   └── wysiwyg-editor.blade.php
│       │   ├── permissions.blade.php
│       │   ├── references.blade.php
│       │   ├── revision.blade.php
│       │   ├── revisions.blade.php
│       │   └── show.blade.php
│       ├── readme.md
│       ├── search/
│       │   ├── all.blade.php
│       │   └── parts/
│       │       ├── boolean-filter.blade.php
│       │       ├── date-filter.blade.php
│       │       ├── entity-selector-list.blade.php
│       │       ├── entity-suggestion-list.blade.php
│       │       ├── term-list.blade.php
│       │       └── type-filter.blade.php
│       ├── settings/
│       │   ├── audit.blade.php
│       │   ├── categories/
│       │   │   ├── customization.blade.php
│       │   │   ├── features.blade.php
│       │   │   ├── registration.blade.php
│       │   │   └── sorting.blade.php
│       │   ├── layout.blade.php
│       │   ├── maintenance.blade.php
│       │   ├── parts/
│       │   │   ├── footer-links.blade.php
│       │   │   ├── navbar.blade.php
│       │   │   ├── setting-color-picker.blade.php
│       │   │   ├── setting-color-scheme.blade.php
│       │   │   └── table-user.blade.php
│       │   ├── recycle-bin/
│       │   │   ├── destroy.blade.php
│       │   │   ├── index.blade.php
│       │   │   ├── parts/
│       │   │   │   ├── deletable-entity-list.blade.php
│       │   │   │   ├── entity-display-item.blade.php
│       │   │   │   └── recycle-bin-list-item.blade.php
│       │   │   └── restore.blade.php
│       │   ├── roles/
│       │   │   ├── create.blade.php
│       │   │   ├── delete.blade.php
│       │   │   ├── edit.blade.php
│       │   │   ├── index.blade.php
│       │   │   └── parts/
│       │   │       ├── asset-permissions-row.blade.php
│       │   │       ├── checkbox.blade.php
│       │   │       ├── form.blade.php
│       │   │       ├── related-asset-permissions-row.blade.php
│       │   │       └── roles-list-item.blade.php
│       │   ├── sort-rules/
│       │   │   ├── create.blade.php
│       │   │   ├── edit.blade.php
│       │   │   └── parts/
│       │   │       ├── form.blade.php
│       │   │       ├── operation.blade.php
│       │   │       └── sort-rule-list-item.blade.php
│       │   └── webhooks/
│       │       ├── create.blade.php
│       │       ├── delete.blade.php
│       │       ├── edit.blade.php
│       │       ├── index.blade.php
│       │       └── parts/
│       │           ├── form.blade.php
│       │           ├── format-example.blade.php
│       │           └── webhooks-list-item.blade.php
│       ├── shelves/
│       │   ├── create.blade.php
│       │   ├── delete.blade.php
│       │   ├── edit.blade.php
│       │   ├── index.blade.php
│       │   ├── parts/
│       │   │   ├── form.blade.php
│       │   │   ├── index-sidebar-section-actions.blade.php
│       │   │   ├── index-sidebar-section-new.blade.php
│       │   │   ├── index-sidebar-section-popular.blade.php
│       │   │   ├── index-sidebar-section-recents.blade.php
│       │   │   ├── list-item.blade.php
│       │   │   ├── list.blade.php
│       │   │   ├── shelf-sort-book-item.blade.php
│       │   │   ├── show-sidebar-section-actions.blade.php
│       │   │   ├── show-sidebar-section-activity.blade.php
│       │   │   ├── show-sidebar-section-details.blade.php
│       │   │   └── show-sidebar-section-tags.blade.php
│       │   ├── permissions.blade.php
│       │   ├── references.blade.php
│       │   └── show.blade.php
│       ├── tags/
│       │   ├── index.blade.php
│       │   └── parts/
│       │       └── tags-list-item.blade.php
│       ├── users/
│       │   ├── account/
│       │   │   ├── auth.blade.php
│       │   │   ├── delete.blade.php
│       │   │   ├── layout.blade.php
│       │   │   ├── notifications.blade.php
│       │   │   ├── parts/
│       │   │   │   └── shortcut-control.blade.php
│       │   │   ├── profile.blade.php
│       │   │   └── shortcuts.blade.php
│       │   ├── api-tokens/
│       │   │   ├── create.blade.php
│       │   │   ├── delete.blade.php
│       │   │   ├── edit.blade.php
│       │   │   └── parts/
│       │   │       ├── form.blade.php
│       │   │       └── list.blade.php
│       │   ├── create.blade.php
│       │   ├── delete.blade.php
│       │   ├── edit.blade.php
│       │   ├── index.blade.php
│       │   ├── parts/
│       │   │   ├── form.blade.php
│       │   │   ├── language-option-row.blade.php
│       │   │   └── users-list-item.blade.php
│       │   └── profile.blade.php
│       └── vendor/
│           ├── notifications/
│           │   ├── email-plain.blade.php
│           │   └── email.blade.php
│           └── pagination/
│               └── default.blade.php
├── routes/
│   ├── api.php
│   └── web.php
├── storage/
│   ├── app/
│   │   └── .gitignore
│   ├── backups/
│   │   └── .gitignore
│   ├── clockwork/
│   │   └── .gitignore
│   ├── fonts/
│   │   └── .gitignore
│   ├── framework/
│   │   ├── .gitignore
│   │   ├── cache/
│   │   │   └── .gitignore
│   │   ├── sessions/
│   │   │   └── .gitignore
│   │   └── views/
│   │       └── .gitignore
│   ├── logs/
│   │   └── .gitignore
│   └── uploads/
│       ├── files/
│       │   └── .gitignore
│       └── images/
│           └── .gitignore
├── tests/
│   ├── Activity/
│   │   ├── AuditLogApiTest.php
│   │   ├── AuditLogTest.php
│   │   ├── CommentDisplayTest.php
│   │   ├── CommentMentionTest.php
│   │   ├── CommentSettingTest.php
│   │   ├── CommentStoreTest.php
│   │   ├── CommentsApiTest.php
│   │   ├── MentionParserTest.php
│   │   ├── WatchTest.php
│   │   ├── WebhookCallTest.php
│   │   ├── WebhookFormatTesting.php
│   │   └── WebhookManagementTest.php
│   ├── Api/
│   │   ├── ApiAuthTest.php
│   │   ├── ApiConfigTest.php
│   │   ├── ApiDocsTest.php
│   │   ├── ApiListingTest.php
│   │   ├── AttachmentsApiTest.php
│   │   ├── BooksApiTest.php
│   │   ├── ChaptersApiTest.php
│   │   ├── ContentPermissionsApiTest.php
│   │   ├── ExportsApiTest.php
│   │   ├── ImageGalleryApiTest.php
│   │   ├── ImportsApiTest.php
│   │   ├── PagesApiTest.php
│   │   ├── RecycleBinApiTest.php
│   │   ├── RolesApiTest.php
│   │   ├── SearchApiTest.php
│   │   ├── ShelvesApiTest.php
│   │   ├── SystemApiTest.php
│   │   ├── TestsApi.php
│   │   └── UsersApiTest.php
│   ├── Auth/
│   │   ├── AuthTest.php
│   │   ├── GroupSyncServiceTest.php
│   │   ├── LdapTest.php
│   │   ├── LoginAutoInitiateTest.php
│   │   ├── MfaConfigurationTest.php
│   │   ├── MfaVerificationTest.php
│   │   ├── OidcTest.php
│   │   ├── RegistrationTest.php
│   │   ├── ResetPasswordTest.php
│   │   ├── Saml2Test.php
│   │   ├── SocialAuthTest.php
│   │   └── UserInviteTest.php
│   ├── Commands/
│   │   ├── AssignSortRuleCommandTest.php
│   │   ├── CleanupImagesCommandTest.php
│   │   ├── ClearActivityCommandTest.php
│   │   ├── ClearRevisionsCommandTest.php
│   │   ├── ClearViewsCommandTest.php
│   │   ├── CopyShelfPermissionsCommandTest.php
│   │   ├── CreateAdminCommandTest.php
│   │   ├── DeleteUsersCommandTest.php
│   │   ├── InstallModuleCommandTest.php
│   │   ├── RefreshAvatarCommandTest.php
│   │   ├── RegeneratePermissionsCommandTest.php
│   │   ├── RegenerateReferencesCommandTest.php
│   │   ├── RegenerateSearchCommandTest.php
│   │   ├── ResetMfaCommandTest.php
│   │   ├── UpdateUrlCommandTest.php
│   │   └── UpgradeDatabaseEncodingCommandTest.php
│   ├── CreatesApplication.php
│   ├── DebugViewTest.php
│   ├── Entity/
│   │   ├── BookShelfTest.php
│   │   ├── BookTest.php
│   │   ├── ChapterTest.php
│   │   ├── ConvertTest.php
│   │   ├── CopyTest.php
│   │   ├── DefaultTemplateTest.php
│   │   ├── EntityAccessTest.php
│   │   ├── EntityQueryTest.php
│   │   ├── MarkdownToHtmlTest.php
│   │   ├── PageContentFilteringTest.php
│   │   ├── PageContentTest.php
│   │   ├── PageDraftTest.php
│   │   ├── PageEditorTest.php
│   │   ├── PageRevisionTest.php
│   │   ├── PageTemplateTest.php
│   │   ├── PageTest.php
│   │   ├── SlugTest.php
│   │   └── TagTest.php
│   ├── ErrorTest.php
│   ├── Exports/
│   │   ├── ExportUiTest.php
│   │   ├── HtmlExportTest.php
│   │   ├── MarkdownExportTest.php
│   │   ├── PdfExportTest.php
│   │   ├── TextExportTest.php
│   │   ├── ZipExportTest.php
│   │   ├── ZipExportValidatorTest.php
│   │   ├── ZipImportRunnerTest.php
│   │   ├── ZipImportTest.php
│   │   ├── ZipResultData.php
│   │   └── ZipTestHelper.php
│   ├── FavouriteTest.php
│   ├── Helpers/
│   │   ├── EntityProvider.php
│   │   ├── FileProvider.php
│   │   ├── OidcJwtHelper.php
│   │   ├── PermissionsProvider.php
│   │   ├── TestServiceProvider.php
│   │   └── UserRoleProvider.php
│   ├── HomepageTest.php
│   ├── LanguageTest.php
│   ├── Meta/
│   │   ├── HelpTest.php
│   │   ├── LicensesTest.php
│   │   ├── OpenGraphTest.php
│   │   ├── OpensearchTest.php
│   │   ├── PwaManifestTest.php
│   │   └── RobotsTest.php
│   ├── Permissions/
│   │   ├── EntityOwnerChangeTest.php
│   │   ├── EntityPermissionsTest.php
│   │   ├── ExportPermissionsTest.php
│   │   ├── RolePermissionsTest.php
│   │   └── Scenarios/
│   │       ├── EntityRolePermissionsTest.php
│   │       ├── PermissionScenarioTestCase.php
│   │       └── RoleContentPermissionsTest.php
│   ├── PublicActionTest.php
│   ├── References/
│   │   ├── CrossLinkParserTest.php
│   │   └── ReferencesTest.php
│   ├── Search/
│   │   ├── EntitySearchTest.php
│   │   ├── SearchIndexingTest.php
│   │   ├── SearchOptionsTest.php
│   │   └── SiblingSearchTest.php
│   ├── SecurityHeaderTest.php
│   ├── SessionTest.php
│   ├── Settings/
│   │   ├── CustomHeadContentTest.php
│   │   ├── FooterLinksTest.php
│   │   ├── PageListLimitsTest.php
│   │   ├── RecycleBinTest.php
│   │   ├── RegenerateReferencesTest.php
│   │   ├── SettingsTest.php
│   │   └── TestEmailTest.php
│   ├── Sorting/
│   │   ├── BookSortTest.php
│   │   ├── MoveTest.php
│   │   └── SortRuleTest.php
│   ├── StatusTest.php
│   ├── TestCase.php
│   ├── Theme/
│   │   ├── LogicalThemeEventsTest.php
│   │   ├── LogicalThemeTest.php
│   │   ├── ThemeModuleTest.php
│   │   └── VisualThemeTest.php
│   ├── Unit/
│   │   ├── ConfigTest.php
│   │   ├── FrameworkAssumptionTest.php
│   │   ├── IpFormatterTest.php
│   │   ├── OidcIdTokenTest.php
│   │   ├── PageIncludeParserTest.php
│   │   └── SsrUrlValidatorTest.php
│   ├── Uploads/
│   │   ├── AttachmentTest.php
│   │   ├── AvatarTest.php
│   │   ├── DrawioTest.php
│   │   ├── ImageStorageTest.php
│   │   └── ImageTest.php
│   ├── UrlTest.php
│   ├── User/
│   │   ├── RoleManagementTest.php
│   │   ├── UserApiTokenTest.php
│   │   ├── UserManagementTest.php
│   │   ├── UserMyAccountTest.php
│   │   ├── UserPreferencesTest.php
│   │   ├── UserProfileTest.php
│   │   └── UserSearchTest.php
│   ├── Util/
│   │   └── DateFormatterTest.php
│   └── test-data/
│       ├── animated.avif
│       ├── bad-php.base64
│       ├── bad-phtml-png.base64
│       ├── bad-phtml.base64
│       └── test-file.txt
├── themes/
│   └── .gitignore
├── tsconfig.json
└── version

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
* text=auto
*.css linguist-vendored
*.less linguist-vendored


================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
  advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

### Project Maintainer Standards

Project maintainers should generally follow these additional standards:

* Avoid using a negative or harsh tone in communication, Even if the other party
is being negative themselves.
* When providing criticism, try to make it constructive to lead the other person
down the correct path.
* Keep the [project definition](https://github.com/BookStackApp/BookStack#project-definition)
in mind when deciding what's in scope of the Project.

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior. In addition, Project
maintainers are responsible for following the standards themselves.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at the email address shown on [the profile here](https://github.com/ssddanbrown). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [ssddanbrown]
ko_fi: ssddanbrown

================================================
FILE: .github/ISSUE_TEMPLATE/api_request.yml
================================================
name: New API Endpoint or API Ability
description: Request a new endpoint or API feature be added
labels: [":nut_and_bolt: API Request"]
body:
  - type: textarea
    id: feature
    attributes:
      label: API Endpoint or Feature
      description: Clearly describe what you'd like to have added to the API.
    validations:
      required: true
  - type: textarea
    id: usecase
    attributes:
      label: Use-Case
      description: Explain the use-case that you're working-on that requires the above request.
    validations:
      required: true
  - type: textarea
    id: context
    attributes:
      label: Additional context
      description: Add any other context about the feature request here.
    validations:
      required: false


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: Create a report to help us fix bugs & issues in existing supported functionality
labels: [":bug: Bug"]
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out a bug report!
        Please note that this form is for reporting bugs in existing supported functionality.
        
        If you are reporting something that's not an issue in functionality we've previously supported and/or is simply something different to your expectations, then it may be more appropriate to raise via a feature or support request instead.
  - type: textarea
    id: description
    attributes:
      label: Describe the Bug
      description: Provide a clear and concise description of what the bug is.
    validations:
      required: true
  - type: textarea
    id: reproduction
    attributes:
      label: Steps to Reproduce
      description: Detail the steps that would replicate this issue.
      placeholder: |
        1. Go to '...'
        2. Click on '....'
        3. Scroll down to '....'
        4. See error
    validations:
      required: true
  - type: textarea
    id: expected
    attributes:
      label: Expected Behaviour
      description: Provide clear and concise description of what you expected to happen.
    validations:
      required: true
  - type: textarea
    id: context
    attributes:
      label: Screenshots or Additional Context
      description: Provide any additional context and screenshots here to help us solve this issue.
    validations:
      required: false
  - type: input
    id: browserdetails
    attributes:
      label: Browser Details
      description: |
        If this is an issue that occurs when using the BookStack interface, please provide details of the browser used which presents the reported issue.
      placeholder: (eg. Firefox 97 (64-bit) on Windows 11)
    validations:
      required: false
  - type: input
    id: bsversion
    attributes:
      label: Exact BookStack Version
      description: This can be found in the settings view of BookStack. Please provide an exact version(s) you've tested on.
      placeholder: (eg. v23.06.7)
    validations:
      required: true


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Discord Chat Support
    url: https://discord.gg/ztkBqR2
    about: Realtime support & chat with the BookStack community and the team.

  - name: Debugging & Common Issues
    url: https://www.bookstackapp.com/docs/admin/debugging/
    about: Find details on how to debug issues and view common issues with their resolutions.

  - name: Official Support Plans
    url: https://www.bookstackapp.com/support/
    about: View our official support plans that offer assured support for business.

================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature Request
description: Request a new feature or idea to be added to BookStack
labels: [":hammer: Feature Request"]
body:
  - type: textarea
    id: description
    attributes:
      label: Describe the feature you'd like
      description: Provide a clear description of the feature you'd like implemented in BookStack
    validations:
      required: true
  - type: textarea
    id: benefits
    attributes:
      label: Describe the benefits this would bring to existing BookStack users
      description: |
        Explain the measurable benefits this feature would achieve for existing BookStack users.
        These benefits should details outcomes in terms of what this request solves/achieves, and should not be specific to implementation.
        This helps us understand the core desired goal so that a variety of potential implementations could be explored.
        This field is important. Lack if input here may lead to early issue closure.
    validations:
      required: true
  - type: textarea
    id: already_achieved
    attributes:
      label: Can the goal of this request already be achieved via other means?
      description: |
        Yes/No. If yes, please describe how the requested approach fits in with the existing method.
    validations:
      required: true
  - type: checkboxes
    id: confirm-search
    attributes:
      label: Have you searched for an existing open/closed issue?
      description: |
        To help us keep these issues under control, please ensure you have first [searched our issue list](https://github.com/BookStackApp/BookStack/issues?q=is%3Aissue) for any existing issues that cover the fundamental benefit/goal of your request.
      options:
        - label: I have searched for existing issues and none cover my fundamental request
          required: true
  - type: dropdown
    id: existing_usage
    attributes:
      label: How long have you been using BookStack?
      options:
        - Not using yet, just scoping
        - Under 3 months
        - 3 months to 1 year
        - 1 to 5 years
        - Over 5 years
    validations:
      required: true
  - type: textarea
    id: context
    attributes:
      label: Additional context
      description: Add any other context or screenshots about the feature request here.
    validations:
      required: false


================================================
FILE: .github/ISSUE_TEMPLATE/language_request.yml
================================================
name: Language Request
description: Request a new language to be added to Crowdin for you to translate
labels: [":earth_africa: Translations"]
assignees:
  - ssddanbrown
body:
  - type: markdown
    attributes:
      value: |
        Thanks for offering to help start a new translation for BookStack!
  - type: input
    id: language
    attributes:
      label: Language to Add
      description: What language (and region if applicable) are you offering to help add to BookStack?
    validations:
      required: true
  - type: checkboxes
    id: confirm
    attributes:
      label: Confirmation of Intent
      description: |
        This issue template is to request a new language be added to our [Crowdin translation management project](https://crowdin.com/project/bookstack).
        Please don't use this template to request a new language that you are not prepared to provide translations for.
      options:
        - label: I confirm I'm offering to help translate for this new language via Crowdin.
          required: true
  - type: markdown
    attributes:
      value: |
        *__Note: New languages are added at specific points of the development process so it may be a small while before the requested language is added for translation.__*


================================================
FILE: .github/ISSUE_TEMPLATE/support_request.yml
================================================
name: Support Request
description: Request support for a specific problem you have not been able to solve yourself
labels: [":dog2: Support"]
body:
  - type: checkboxes
    id: useddocs
    attributes:
      label: Attempted Debugging
      description: |
        I have read the [BookStack debugging](https://www.bookstackapp.com/docs/admin/debugging/) page and seeked resolution or more
        detail for the issue.
      options:
        - label: I have read the debugging page
          required: true
  - type: checkboxes
    id: searchissue
    attributes:
      label: Searched GitHub Issues
      description: |
        I have searched for the issue and potential resolutions within the [project's GitHub issue list](https://github.com/BookStackApp/BookStack/issues)
      options:
        - label: I have searched GitHub for the issue.
          required: true
  - type: textarea
    id: scenario
    attributes:
      label: Describe the Scenario
      description: Detail the problem that you're having or what you need support with.
    validations:
      required: true
  - type: input
    id: bsversion
    attributes:
      label: Exact BookStack Version
      description: This can be found in the settings view of BookStack. Please provide an exact version.
      placeholder: (eg. v23.06.7)
    validations:
      required: true
  - type: textarea
    id: logs
    attributes:
      label: Log Content
      description: If the issue has produced an error, provide any [BookStack or server log](https://www.bookstackapp.com/docs/admin/debugging/) content below.
      placeholder: Be sure to remove any confidential details in your logs
      render: text
    validations:
      required: false
  - type: textarea
    id: hosting
    attributes:
      label: Hosting Environment
      description: Describe your hosting environment as much as possible including any proxies used (If applicable).
      placeholder: (eg. PHP8.1 on Ubuntu 22.04 VPS, installed using official installation script)
    validations:
      required: true


================================================
FILE: .github/ISSUE_TEMPLATE/z_blank_request.yml
================================================
name: Blank Request (Maintainers Only)
description: For maintainers only - Start a blank request
body:
  - type: markdown
    attributes:
      value: "**This blank request option is only for existing official maintainers of the project!** Please instead use a different request option. If you use this your issue will be closed off."
  - type: textarea
    attributes:
      label: Description

================================================
FILE: .github/SECURITY.md
================================================
# Security Policy

## Supported Versions

Only the [latest version](https://github.com/BookStackApp/BookStack/releases) of BookStack is supported.
We generally don't support older versions of BookStack due to maintenance effort and
since we aim to provide a fairly stable upgrade path for new versions.

## Security Notifications

If you'd like to be notified of new potential security concerns you can [sign-up to the BookStack security mailing list](https://updates.bookstackapp.com/signup/bookstack-security-updates).

## Reporting a Vulnerability

If you've found an issue that likely has no impact to existing users (For example, in a development-only branch)
feel free to raise it via a standard GitHub bug report issue.

If the issue could have a security impact to BookStack instances, 
please directly contact the lead maintainer [@ssddanbrown](https://github.com/ssddanbrown). 
You will need to log in to be able to see the email address on the [GitHub profile page](https://github.com/ssddanbrown).
Alternatively you can send a DM via Mastodon to [@danb@fosstodon.org](https://fosstodon.org/@danb).

Please be patient while the vulnerability is being reviewed. Deploying the fix to address the vulnerability
can often take a little time due to the amount of preparation required, to ensure the vulnerability has
been covered, and to create the content required to adequately notify the user-base.

Thank you for keeping BookStack instances safe!


================================================
FILE: .github/translators.txt
================================================
Name :: Languages
@robertlandes :: German
@SergioMendolia :: French
@NakaharaL :: Portuguese, Brazilian
@ReeseSebastian :: German
@arietimmerman :: Dutch
@diegoseso :: Spanish
@S64 :: Japanese
@JachuPL :: Polish
@Joorem :: French
@timoschwarzer :: German
@sanderdw :: Dutch
@lbguilherme :: Portuguese, Brazilian
@marcusforsberg :: Swedish
@artur-trzesiok :: Polish
@Alwaysin :: French
@msaus :: Japanese
@moucho :: Spanish
@vriic :: German
@DeehSlash :: Portuguese, Brazilian
@alex2702 :: German
@nicobubulle :: French
@kmoj86 :: Arabic
@houbaron :: Chinese Traditional; Chinese Simplified
@mullinsmikey :: Russian
@limkukhyun :: Korean
@CliffyPrime :: German
@kejjang :: Chinese Traditional
@TheLastOperator :: French
@qianmengnet :: Simplified Chinese
@ezzra :: German; German Informal
@vasiliev123 :: Polish
@Mant1kor :: Ukrainian
@Xiphoseer :: German; German Informal
@maantje :: Dutch
@cima :: Czech
@agvol :: Russian
@Hambern :: Swedish
@NootoNooto :: Dutch
@kostefun :: Russian
@lucaguindani :: French
@miles75 :: Hungarian
@danielroehrig-mm :: German
@oykenfurkan :: Turkish
@qligier :: French
@johnroyer :: Traditional Chinese
@artskoczylas :: Polish
@dellamina :: Italian
@jzoy :: Simplified Chinese
@ististudio :: Korean
@leomartinez :: Spanish Argentina
@geins :: German
@Ereza :: Catalan
@benediktvolke :: German
@Baptistou :: French
@arcoai :: Spanish
@Jokuna :: Korean
@smartshogu :: German; German Informal
@samadha56 :: Persian
@mrmuminov :: Uzbek
cipi1965 :: Italian
Mykola Ronik (Mantikor) :: Ukrainian
furkanoyk :: Turkish
m0uch0 :: Spanish
Maxim Zalata (zlatin) :: Russian; Ukrainian
nutsflag :: French
Leonardo Mario Martinez (leonardo.m.martinez) :: Spanish, Argentina
Rodrigo Saczuk Niz (rodrigoniz) :: Portuguese, Brazilian
叫钦叔就好 (254351722) :: Chinese Traditional; Chinese Simplified
aekramer :: Dutch
JachuPL :: Polish
milesteg :: Hungarian
Beenbag :: German; German Informal
Lett3rs :: Danish
Julian (julian.henneberg) :: German; German Informal
3GNWn :: Danish
dbguichu :: Chinese Simplified
Randy Kim (hyunjun) :: Korean
Francesco M. Taurino (ftaurino) :: Italian
DanielFrederiksen :: Danish
Finn Wessel (19finnwessel6) :: German Informal; German
Gustav Kånåhols (Kurbitz) :: Swedish
Vuong Trung Hieu (fpooon) :: Vietnamese
Emil Petersen (emoyly) :: Danish
mrjaboozy :: Slovenian
Statium :: Russian
Mikkel Struntze (MStruntze) :: Danish
kostefun :: Russian
Tuyen.NG (tuyendev) :: Vietnamese
Ghost_chu (dbguichu) :: Chinese Simplified
Ziipen :: Danish
Samuel Schwarz (Guiph7quan) :: Czech
Aleph (toishoki) :: Turkish
Julio Alberto García (Yllelder) :: Spanish
Rafael (raribeir) :: Portuguese, Brazilian
Hiroyuki Odake (dakesan) :: Japanese
Alex Lee (qianmengnet) :: Chinese Simplified
swinn37 :: French
Hasan Özbey (the-turk) :: Turkish
rcy :: Swedish
Ali Yasir Yılmaz (ayyilmaz) :: Turkish
scureza :: Italian
Biepa :: German Informal; German
syecu :: Chinese Simplified
Lap1t0r :: French
Thinkverse (thinkverse) :: Swedish
alef (toishoki) :: Turkish
Robbert Feunekes (Muukuro) :: Dutch
seohyeon.joo :: Korean
Orenda (OREDNA) :: Bulgarian
Marek Pavelka (marapavelka) :: Czech
Venkinovec :: Czech
Tommy Ku (tommyku) :: Chinese Traditional; Japanese
Michał Bielejewski  (bielej) :: Polish
jozefrebjak :: Slovak
Ikhwan Koo (Ikhwan.Koo) :: Korean
Whay (remkovdhoef) :: Dutch
jc7115 :: Chinese Traditional
주서현 (seohyeon.joo) :: Korean
ReadySystems :: Arabic
HFinch :: German; German Informal
brechtgijsens :: Dutch
Lowkey (v587ygq) :: Chinese Simplified
sdl-blue :: German Informal
sqlik :: Polish
Roy van Schaijk (royvanschaijk) :: Dutch
Simsimpicpic :: French
Zenahr Barzani (Zenahr) :: German; Japanese; Dutch; German Informal
tatsuya.info :: Japanese
fadiapp :: Arabic
Jakub Bouček (jakubboucek) :: Czech
Marco (cdrfun) :: German; German Informal
10935336 :: Chinese Simplified
孟繁阳 (FanyangMeng) :: Chinese Simplified
Andrej Močan (andrejm) :: Slovenian
gilane9_ :: Arabic
Raed alnahdi (raednahdi) :: Arabic
Xiphoseer :: German
MerlinSVK (merlinsvk) :: Slovak
Kauê Sena (kaue.sena.ks) :: Portuguese, Brazilian
MatthieuParis :: French
Douradinho :: Portuguese, Brazilian; Portuguese
Gaku Yaguchi (tama11) :: Japanese
Zero Huang (johnroyer) :: Chinese Traditional
jackaaa :: Chinese Traditional
Irfan Hukama Arsyad (IrfanArsyad) :: Indonesian
Jeff Huang (s8321414) :: Chinese Traditional
Luís Tiago Favas (starkyller) :: Portuguese
semirte :: Bosnian
aarchijs :: Latvian
Martins Pilsetnieks (pilsetnieks) :: Latvian
Yonatan Magier (yonatanmgr) :: Hebrew
FastHogi :: German Informal; German
Ole Anders (Swoy) :: Norwegian Bokmal
Atlochowski (atlochowski) :: Polish
Simon (DefaultSimon) :: Slovenian
Reinis Mednis (Mednis) :: Latvian
toisho (toishoki) :: Turkish
nikservik :: Ukrainian; Russian; Polish
HenrijsS :: Latvian
Pascal R-B (pborgner) :: German
Boris (Ginfred) :: Russian
Jonas Anker Rasmussen (jonasanker) :: Danish
Gerwin de Keijzer (gdekeijzer) :: Dutch; German Informal; German
kometchtech :: Japanese
Auri (Atalonica) :: Catalan
Francesco Franchina (ffranchina) :: Italian
Aimrane Kds (aimrane.kds) :: Arabic
whenwesober :: Indonesian
Rem (remkovdhoef) :: Dutch
syn7ax69 :: Bulgarian; Turkish; German
Blaade :: French
Behzad HosseinPoor (behzad.hp) :: Persian
Ole Aldric (Swoy) :: Norwegian Bokmal
fharis arabia (raednahdi) :: Arabic
Alexander Predl (Harveyhase68) :: German
Rem (Rem9000) :: Dutch
Michał Stelmach (stelmach-web) :: Polish
arniom :: French
REMOVED_USER :: French; German; Dutch; Portuguese, Brazilian; Portuguese; Turkish; 
林祖年 (contagion) :: Chinese Traditional
Siamak Guodarzi (siamakgoudarzi88) :: Persian
Lis Maestrelo (lismtrl) :: Portuguese, Brazilian
Nathanaël (nathanaelhoun) :: French
A Ibnu Hibban (abd.ibnuhibban) :: Indonesian
Frost-ZX :: Chinese Simplified
Kuzma Simonov (ovmach) :: Russian
Vojtěch Krystek (acantophis) :: Czech
Michał Lipok (mLipok) :: Polish
Nicolas Pawlak (Mikolajek) :: French; Polish; German
Thomas Hansen (thomasdk81) :: Danish
Hl2run :: Slovak
Ngo Tri Hoai (trihoai) :: Vietnamese
Atalonica :: Catalan
慕容潭谈 (591442386) :: Chinese Simplified
Radim Pesek (ramess18) :: Czech
anastasiia.motylko :: Ukrainian
Indrek Haav (IndrekHaav) :: Estonian
na3shkw :: Japanese
Giancarlo Di Massa (digitall-it) :: Italian
M Nafis Al Mukhdi (mnafisalmukhdi1) :: Indonesian
sulfo :: Danish
Raukze :: German
zygimantus :: Lithuanian
marinkaberg :: Russian
Vitaliy (gviabcua) :: Ukrainian
mannycarreiro :: Portuguese
Thiago Rafael Pereira de Carvalho (thiago.rafael) :: Portuguese, Brazilian
Ken Roger Bolgnes (kenbo124) :: Norwegian Bokmal
Nguyen Hung Phuong (hnwolf) :: Vietnamese
Umut ERGENE (umutergene67) :: Turkish
Tomáš Batelka (Vofy) :: Czech
Mundo Racional (ismael.mesquita) :: Portuguese, Brazilian
Zarik (3apuk) :: Russian
Ali Shaatani (a.shaatani) :: Arabic
ChacMaster :: Portuguese, Brazilian
Saeed (saeed205) :: Persian
Julesdevops :: French
peter cerny (posli.to.semka) :: Slovak
Pavel Karlin (pavelkarlin) :: Russian
SmokingCrop :: Dutch
Maciej Lebiest (Szwendacz) :: Polish
DiscordDigital :: German; German Informal
Gábor Marton (dodver) :: Hungarian
Jakob Åsell (Jasell) :: Swedish
Ghost_chu (ghostchu) :: Chinese Simplified
Ravid Shachar (ravidshachar) :: Hebrew
Helga Guchshenskaya (guchshenskaya) :: Russian
daniel chou (chou0214) :: Chinese Traditional
Manolis PATRIARCHE (m.patriarche) :: French
Mohammed Haboubi (haboubi92) :: Arabic
roncallyt :: Portuguese, Brazilian
goegol :: Dutch
msevgen :: Turkish
Khroners :: French
MASOUD HOSSEINY (masoudme) :: Persian
Thomerson Roncally (roncallyt) :: Portuguese, Brazilian
metaarch :: Bulgarian
Xabi (xabikip) :: Basque
pedromcsousa :: Portuguese
Nir Louk (looknear) :: Hebrew
Alex (qianmengnet) :: Chinese Simplified
stothew :: German
sgenc :: Turkish
Shukrullo (vodiylik) :: Uzbek
William W. (Nevnt) :: Chinese Traditional
eamaro :: Portuguese
Ypsilon-dev :: Arabic
Hieu Vuong Trung (vuongtrunghieu) :: Vietnamese
David Clubb (davidoclubb) :: Welsh
welles freire (wellesximenes) :: Portuguese, Brazilian
Magnus Jensen (MagnusHJensen) :: Danish
Hesley Magno (hesleymagno) :: Portuguese, Brazilian
Éric Gaspar (erga) :: French
Fr3shlama :: German
DSR :: Spanish, Argentina
Andrii Bodnar (andrii-bodnar) :: Ukrainian
Younes el Anjri (younesea28) :: Dutch
Guclu Ozturk (gucluoz) :: Turkish
Atmis :: French
redjack666 :: Chinese Traditional
Ashita007 :: Russian
lihaorr :: Chinese Simplified
Marcus Silber (marcus.silber82) :: German
PellNet :: Croatian
Winetradr :: German
Sebastian Klaus (sebklaus) :: German
Filip Antala (AntalaFilip) :: Slovak
mcgong (GongMingCai) :: Chinese Simplified; Chinese Traditional
Nanang Setia Budi (sefidananang) :: Indonesian
Андрей Павлов (andrei.pavlov) :: Russian
Alex Navarro (alex.n.navarro) :: Portuguese, Brazilian
Jihyeon Gim (PotatoGim) :: Korean
Mihai Ochian (soulstorm19) :: Romanian
HeartCore :: German Informal; German
simon.pct :: French
okaeiz :: Persian
Naoto Ishikawa (na3shkw) :: Japanese
sdhadi :: Persian
DerLinkman (derlinkman) :: German; German Informal
TurnArabic :: Arabic
Martin Sebek (sebekmartin) :: Czech
Kuchinashi Hoshikawa (kuchinashi) :: Chinese Simplified
digilady :: Greek
Linus (LinusOP) :: Swedish
Felipe Cardoso (felipecardosoruff) :: Portuguese, Brazilian
RandomUser0815 :: German Informal; German
Ismael Mesquita (mesquitoliveira) :: Portuguese, Brazilian
구인회 (laskdjlaskdj12) :: Korean
LiZerui (CNLiZerui) :: Chinese Traditional
Fabrice Boyer (FabriceBoyer) :: French
mikael (bitcanon) :: Swedish
Matthias Mai (schnapsidee) :: German Informal; German
Ufuk Ayyıldız (ufukayyildiz) :: Turkish
Jan Mitrof (jan.kachlik) :: Czech
edwardsmirnov :: Russian
Mr_OSS117 :: French
shotu :: French
Cesar_Lopez_Aguillon :: Spanish
bdewoop :: German
dina davoudi (dina.davoudi) :: Persian
Angelos Chouvardas (achouvardas) :: Greek
rndrss :: Portuguese, Brazilian
rirac294 :: Russian
David Furman (thefourCraft) :: Hebrew
Pafzedog :: French
Yllelder :: Spanish
Adrian Ocneanu (aocneanu) :: Romanian
Eduardo Castanho (EduardoCastanho) :: Portuguese
VIET NAM VPS (vietnamvps) :: Vietnamese
m4tthi4s :: French
toras9000 :: Japanese
pathab :: German
MichelSchoon85 :: Dutch
Jøran Haugli (haugli92) :: Norwegian Bokmal
Vasileios Kouvelis (VasilisKouvelis) :: Greek
Dremski :: Bulgarian
Frédéric SENE (nothingfr) :: French
bendem :: French
kostasdizas :: Greek
Ricardo Schroeder (brownstone666) :: Portuguese, Brazilian
Eitan MG (EitanMG) :: Hebrew
Robin Flikkema (RobinFlikkema) :: Dutch
Michal Gurcik (mgurcik) :: Slovak
Pooyan Arab (pooyanarab) :: Persian
Ochi Darma Putra (troke12) :: Indonesian
Hsin-Hsiang Peng (Hsins) :: Chinese Traditional
Mosi  Wang (mosiwang) :: Chinese Traditional
骆言 (LawssssCat) :: Chinese Simplified
Stickers Gaming Shøw (StickerSGSHOW) :: French
Le Van Chinh (Chino) (lvanchinh86) :: Vietnamese
Rubens nagios (rubenix) :: Catalan
Patrick Dantas (pa-tiq) :: Portuguese, Brazilian
Michal (michalgurcik) :: Slovak
Nepomacs :: German
Rubens (rubenix) :: Catalan
m4z :: German; German Informal
TheRazvy :: Romanian
Yossi Zilber (lortens) :: Hebrew; Uzbek
desdinova :: French
Ingus Rūķis (ingus.rukis) :: Latvian
Eugene Pershin (SilentEugene) :: Russian
周盛道 (zhoushengdao) :: Chinese Simplified
hamidreza amini (hamidrezaamini2022) :: Persian
Tomislav Kraljević (tomislav.kraljevic) :: Croatian
Taygun Yıldırım (yildirimtaygun) :: Turkish
robing29 :: German
Bruno Eduardo de Jesus Barroso (brunoejb) :: Portuguese, Brazilian
Igor V Belousov (biv) :: Russian
David Bauer (davbauer) :: German; German Informal
Guttorm Hveem (guttormhveem) :: Norwegian Nynorsk; Norwegian Bokmal
Minh Giang Truong (minhgiang1204) :: Vietnamese
Ioannis Ioannides (i.ioannides) :: Greek
Vadim (vadrozh) :: Russian
Flip333 :: German Informal; German
Paulo Henrique (paulohsantos114) :: Portuguese, Brazilian
Dženan (Dzenan) :: Swedish
Péter Péli (peter.peli) :: Hungarian
TWME :: Chinese Traditional
Sascha (Man-in-Black) :: German; German Informal
Mohammadreza Madadi (madadi.efl) :: Persian
Konstantin (kkovacheli) :: Ukrainian; Russian
link1183 :: French
Renan (rfpe) :: Portuguese, Brazilian
Lowkey (bbsweb) :: Chinese Simplified
ZZnOB (zznobzz) :: Russian
rupus :: Swedish
developernecsys :: Norwegian Nynorsk
xuan LI (xuanli233) :: Chinese Simplified
LameeQS :: Latvian
Sorin T. (trimbitassorin) :: Romanian
poesty :: Chinese Simplified
balmag :: Hungarian
Antti-Jussi Nygård (ajnyga) :: Finnish
Eduard Ereza Martínez (Ereza) :: Catalan
Jabir Lang (amar.almrad) :: Arabic
Jaroslav Kobližek (foretix) :: Czech; French
Wiktor Adamczyk (adamczyk.wiktor) :: Polish
Abdulmajeed Alshuaibi (4Majeed) :: Arabic
NotSmartZakk :: Czech
HyoungMin Lee (ddokkaebi) :: Korean
Dasferco :: Chinese Simplified
Marcus Teräs (mteras) :: Finnish
Serkan Yardim (serkanzz) :: Turkish
Y (cnsr) :: Ukrainian
ZY ZV (vy0b0x) :: Chinese Simplified
diegobenitez :: Spanish
Marc Hagen (MarcHagen) :: Dutch
Kasper Alsøe (zeonos) :: Danish
sultani :: Persian
renge :: Korean
Tim (thegatesdev) :: Dutch; German Informal; French; Romanian; Catalan; Czech; Danish; German; Finnish; Hungarian; Italian; Japanese; Korean; Polish; Russian; Ukrainian; Chinese Simplified; Chinese Traditional; Portuguese, Brazilian; Persian; Spanish, Argentina; Croatian; Norwegian Nynorsk; Estonian; Uzbek; Norwegian Bokmal
Irdi (irdiOL) :: Albanian
KateBarber :: Welsh
Twister (theuncles75) :: Hebrew
algernon19 :: Hungarian
Ivan Krstic (ikrstic) :: Serbian (Cyrillic)
Show :: Russian
xBahamut :: Portuguese, Brazilian
Pavle Knežević (pavleknezzevic) :: Serbian (Cyrillic)
Vanja Cvelbar (b100w11) :: Slovenian
simonpct :: French
Honza Nagy (honza.nagy) :: Czech
asd20752 :: Norwegian Bokmal
Jan Picka (polipones) :: Czech
diogoalex991 :: Portuguese
Ehsan Sadeghi (ehsansadeghi) :: Persian
ka_picit :: Danish
cracrayol :: French
CapuaSC :: Dutch
Guardian75 :: German Informal
mr-kanister :: German
Michele Bastianelli (makoblaster) :: Italian
jespernissen :: Danish
Andrey (avmaksimov) :: Russian
Gonzalo Loyola (AlFcl) :: Spanish, Argentina; Spanish
grobert63 :: French
wusst. (Supporti) :: German
MaximMaximS :: Czech
damian-klima :: Slovak
crow_ :: Latvian
JocelynDelalande :: French
Jan (JW-CH) :: German Informal
Timo B (lommes) :: German Informal
Erik Lundstedt (Erik.Lundstedt) :: Swedish
yngams (younessmouhid) :: Arabic
Ohadp :: Hebrew
cbridi :: Portuguese, Brazilian
nanangsb :: Indonesian
Michal Melich (michalmelich) :: Czech
David (david-prv) :: German; German Informal
Larry (lahoje) :: Swedish
Marcia dos Santos (marciab80) :: Portuguese
Ricard López Torres (richilpez.torres) :: Catalan
sarahalves7 :: Portuguese, Brazilian
petr.husak :: Czech
javadataherian :: Persian
Ludo-code :: French
hollsten :: Swedish
Ngoc Lan Phung (lanpncz) :: Vietnamese
Worive :: Catalan; French
Илья Скаба (skabailya) :: Russian
Irjan Olsen (Irch) :: Norwegian Bokmal
Aleksandar Jovanovic (jovanoviczaleksandar) :: Serbian (Cyrillic)
Red (RedVortex) :: Hebrew
xgrug :: Chinese Simplified
HrCalmar :: Danish
Avishay Rapp (AvishayRapp) :: Hebrew
matthias4217 :: French
Berke BOYLU2 (berkeboylu2) :: Turkish
etwas7B :: German
Mohammed srhiri (m.sghiri20) :: Arabic
YongMin Kim (kym0118) :: Korean
Rivo Zängov (Eraser) :: Estonian
Francisco Rafael Fonseca (chicoraf) :: Portuguese, Brazilian
ИEØ_ΙΙØZ (NEO_IIOZ) :: Chinese Traditional
madnjpn (madnjpn.) :: Georgian
Ásgeir Shiny Ásgeirsson (AsgeirShiny) :: Icelandic
Mohammad Aftab Uddin (chirohorit) :: Bengali
Yannis Karlaftis (meliseus) :: Greek
felixxx :: German Informal
randi (randi65535) :: Korean
test65428 :: Greek
zeronell :: Chinese Simplified
julien Vinber (julienVinber) :: French
Hyunwoo Park (oksure) :: Korean
aram.rafeq.7 (aramrafeq2) :: Kurdish
Raphael Moreno (RaphaelMoreno) :: Portuguese, Brazilian
yn (user99) :: Arabic
Pavel Zlatarov (pzlatarov) :: Bulgarian
ingelres :: French
mabdullah :: Arabic
Skrabák Csaba (kekcsi) :: Hungarian
Evert Meulie (Evert) :: Norwegian Bokmal
Jasper Backer (jasperb) :: Dutch
Alexandar Cavdarovski (ace.200112) :: Swedish
구닥다리TV (yjj8353) :: Korean
Onur Oskay (o.oskay) :: Turkish
Sébastien Merveille (SebastienMerv) :: French
Maxim Kouznetsov (masya.work) :: Hebrew
neodvisnost :: Slovenian
Soubi Agatsuma (bisouya) :: Hebrew
Ilya Shaulov (ishaulov) :: Russian
Konstantin Bobkov (b.konstantv) :: Russian
Ruben Sutter (rubensutter) :: German
jellium :: French
Qxlkdr :: Swedish
Hari (muhhari) :: Indonesian
仙君御 (xjy) :: Chinese Simplified
TapioM :: Finnish
lingb58 :: Chinese Traditional
Angel Pandey (angel-pandey) :: Nepali
Supriya Shrestha (supriyashrestha) :: Nepali
gprabhat :: Nepali
CellCat :: Chinese Simplified
Al Desrahim (aldesrahim) :: Indonesian
ahmad abbaspour (deshneh.dar.diss) :: Persian
Erjon K. (ekr) :: Albanian
LiZerui (iamzrli) :: Chinese Traditional
Ticker (ticker.com) :: Hebrew
CrazyComputer :: Chinese Simplified
Firr (FirrV) :: Russian
João Faro (FaroJoaoFaro) :: Portuguese
Danilo dos Santos Barbosa (bozochegou) :: Portuguese, Brazilian
Chris (furesoft) :: German
Silvia Isern (eiendragon) :: Catalan
Dennis Kron Pedersen (ahjdp) :: Danish
iamwhoiamwhoami :: Swedish
Grogui :: French
MrCharlesIII :: Arabic
David Olsen (dawin) :: Danish
ltnzr :: French
Frank Holler (holler.frank) :: German; German Informal
Korab Arifi (korabidev) :: Albanian
Petr Husák (petrhusak) :: Czech
Bernardo Maia (bernardo.bmaia2) :: Portuguese, Brazilian
Amr (amr3k) :: Arabic
Tahsin Ahmed (tahsinahmed2012) :: Bengali
bojan_che :: Serbian (Cyrillic)
setiawan setiawan (culture.setiawan) :: Indonesian
Donald Mac Kenzie (kiuman) :: Norwegian Bokmal
Gabriel Silver (GabrielBSilver) :: Hebrew
Tomas Darius Davainis (Tomasdd) :: Lithuanian
CriedHero :: Chinese Simplified
Henrik (henrik2105) :: Norwegian Bokmal
FoW (fofwisdom) :: Korean
serinf-lauza :: French
Diyan Nikolaev (nikolaev.diyan) :: Bulgarian
Shadluk Avan (quldosh) :: Uzbek
Marci (MartonPoto) :: Hungarian
Michał Sadurski (wheeskeey) :: Polish
JanDziaslo :: Polish
Charllys Fernandes (CharllysFernandes) :: Portuguese, Brazilian
Ilgiz Zigangirov (inov8) :: Russian
Max Israelsson (Blezie) :: Swedish


================================================
FILE: .github/workflows/analyse-php.yml
================================================
name: analyse-php

on:
  push:
    paths:
      - '**.php'
  pull_request:
    paths:
      - '**.php'

jobs:
  build:
    if: ${{ github.ref != 'refs/heads/l10n_development' }}
    runs-on: ubuntu-24.04
    steps:
    - uses: actions/checkout@v4

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: 8.3
        extensions: gd, mbstring, json, curl, xml, mysql, ldap

    - name: Get Composer Cache Directory
      id: composer-cache
      run: |
        echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

    - name: Cache composer packages
      uses: actions/cache@v4
      with:
        path: ${{ steps.composer-cache.outputs.dir }}
        key: ${{ runner.os }}-composer-8.3
        restore-keys: ${{ runner.os }}-composer-

    - name: Install composer dependencies
      run: composer install --prefer-dist --no-interaction --ansi

    - name: Run static analysis check
      run: composer check-static


================================================
FILE: .github/workflows/lint-js.yml
================================================
name: lint-js

on:
  push:
    paths:
      - '**.js'
      - '**.json'
  pull_request:
    paths:
      - '**.js'
      - '**.json'

jobs:
  build:
    if: ${{ github.ref != 'refs/heads/l10n_development' }}
    runs-on: ubuntu-24.04
    steps:
    - uses: actions/checkout@v4

    - name: Install NPM deps
      run: npm ci

    - name: Run formatting check
      run: npm run lint


================================================
FILE: .github/workflows/lint-php.yml
================================================
name: lint-php

on:
  push:
    paths:
      - '**.php'
  pull_request:
    paths:
      - '**.php'

jobs:
  build:
    if: ${{ github.ref != 'refs/heads/l10n_development' }}
    runs-on: ubuntu-24.04
    steps:
    - uses: actions/checkout@v4

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: 8.3
        tools: phpcs

    - name: Run formatting check
      run: composer lint


================================================
FILE: .github/workflows/test-js.yml
================================================
name: test-js

on:
  push:
    paths:
      - '**.js'
      - '**.ts'
      - '**.json'
  pull_request:
    paths:
      - '**.js'
      - '**.ts'
      - '**.json'

jobs:
  build:
    if: ${{ github.ref != 'refs/heads/l10n_development' }}
    runs-on: ubuntu-24.04
    steps:
    - uses: actions/checkout@v4

    - name: Install NPM deps
      run: npm ci

    - name: Run TypeScript type checking
      run: npm run ts:lint

    - name: Run JavaScript tests
      run: npm run test

================================================
FILE: .github/workflows/test-migrations.yml
================================================
name: test-migrations

on:
  push:
    paths:
      - '**.php'
      - 'composer.*'
  pull_request:
    paths:
      - '**.php'
      - 'composer.*'

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

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: gd, mbstring, json, curl, xml, mysql, ldap

      - name: Get Composer Cache Directory
        id: composer-cache
        run: |
          echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

      - name: Cache composer packages
        uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ matrix.php }}
          restore-keys: ${{ runner.os }}-composer-

      - name: Start MySQL
        run: |
          sudo systemctl start mysql

      - name: Create database & user
        run: |
          mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS `bookstack-test`;'
          mysql -uroot -proot -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED WITH mysql_native_password BY 'bookstack-test';"
          mysql -uroot -proot -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
          mysql -uroot -proot -e 'FLUSH PRIVILEGES;'

      - name: Install composer dependencies
        run: composer install --prefer-dist --no-interaction --ansi

      - name: Start migration test
        run: |
          php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing

      - name: Start migration:rollback test
        run: |
          php${{ matrix.php }} artisan migrate:rollback --force -n --database=mysql_testing

      - name: Start migration rerun test
        run: |
          php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing


================================================
FILE: .github/workflows/test-php.yml
================================================
name: test-php

on:
  push:
    paths:
      - '**.php'
      - 'composer.*'
  pull_request:
    paths:
      - '**.php'
      - 'composer.*'

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

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: ${{ matrix.php }}
        extensions: gd, mbstring, json, curl, xml, mysql, ldap, gmp

    - name: Get Composer Cache Directory
      id: composer-cache
      run: |
        echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

    - name: Cache composer packages
      uses: actions/cache@v4
      with:
        path: ${{ steps.composer-cache.outputs.dir }}
        key: ${{ runner.os }}-composer-${{ matrix.php }}
        restore-keys: ${{ runner.os }}-composer-

    - name: Start Database
      run: |
        sudo systemctl start mysql

    - name: Setup Database
      run: |
        mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS `bookstack-test`;'
        mysql -uroot -proot -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED WITH mysql_native_password BY 'bookstack-test';"
        mysql -uroot -proot -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
        mysql -uroot -proot -e 'FLUSH PRIVILEGES;'

    - name: Install composer dependencies
      run: composer install --prefer-dist --no-interaction --ansi

    - name: Migrate and seed the database
      run: |
        php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing
        php${{ matrix.php }} artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing

    - name: Run PHP tests
      run: php${{ matrix.php }} ./vendor/bin/phpunit


================================================
FILE: .gitignore
================================================
/vendor
/node_modules
/.vscode
/composer
/coverage
Homestead.yaml
.env
.idea
npm-debug.log
yarn-error.log
/public/dist
/public/plugins
/public/css
/public/js
/public/bower
/public/build/
/public/favicon.ico
/storage/images
_ide_helper.php
/storage/debugbar
.phpstorm.meta.php
yarn.lock
/bin
nbproject
.buildpath
.project
.nvmrc
.settings/
webpack-stats.json
.phpunit.result.cache
.DS_Store
phpstan.neon
esbuild-meta.json
.phpactor.json
/*.zip


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2015-2026, Dan Brown and the BookStack project contributors.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: app/Access/Controllers/ConfirmEmailController.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Access\EmailConfirmationService;
use BookStack\Access\LoginService;
use BookStack\Exceptions\ConfirmationEmailException;
use BookStack\Exceptions\UserTokenExpiredException;
use BookStack\Exceptions\UserTokenNotFoundException;
use BookStack\Http\Controller;
use BookStack\Users\UserRepo;
use Exception;
use Illuminate\Http\Request;

class ConfirmEmailController extends Controller
{
    public function __construct(
        protected EmailConfirmationService $emailConfirmationService,
        protected LoginService $loginService,
        protected UserRepo $userRepo
    ) {
    }

    /**
     * Show the page to tell the user to check their email
     * and confirm their address.
     */
    public function show()
    {
        return view('auth.register-confirm');
    }

    /**
     * Shows a notice that a user's email address has not been confirmed,
     * along with the option to re-send the confirmation email.
     */
    public function showAwaiting()
    {
        $user = $this->loginService->getLastLoginAttemptUser();
        if ($user === null) {
            $this->showErrorNotification(trans('errors.login_user_not_found'));
            return redirect('/login');
        }

        return view('auth.register-confirm-awaiting');
    }

    /**
     * Show the form for a user to provide their positive confirmation of their email.
     */
    public function showAcceptForm(string $token)
    {
        return view('auth.register-confirm-accept', ['token' => $token]);
    }

    /**
     * Confirms an email via a token and logs the user into the system.
     *
     * @throws ConfirmationEmailException
     * @throws Exception
     */
    public function confirm(Request $request)
    {
        $validated = $this->validate($request, [
            'token' => ['required', 'string']
        ]);

        $token = $validated['token'];

        try {
            $userId = $this->emailConfirmationService->checkTokenAndGetUserId($token);
        } catch (UserTokenNotFoundException $exception) {
            $this->showErrorNotification(trans('errors.email_confirmation_invalid'));

            return redirect('/register');
        } catch (UserTokenExpiredException $exception) {
            $user = $this->userRepo->getById($exception->userId);
            $this->emailConfirmationService->sendConfirmation($user);
            $this->showErrorNotification(trans('errors.email_confirmation_expired'));

            return redirect('/register/confirm');
        }

        $user = $this->userRepo->getById($userId);
        $user->email_confirmed = true;
        $user->save();

        $this->emailConfirmationService->deleteByUser($user);
        $this->showSuccessNotification(trans('auth.email_confirm_success'));

        return redirect('/login');
    }

    /**
     * Resend the confirmation email.
     */
    public function resend()
    {
        $user = $this->loginService->getLastLoginAttemptUser();
        if ($user === null) {
            $this->showErrorNotification(trans('errors.login_user_not_found'));
            return redirect('/login');
        }

        try {
            $this->emailConfirmationService->sendConfirmation($user);
        } catch (ConfirmationEmailException $e) {
            $this->showErrorNotification($e->getMessage());

            return redirect('/login');
        } catch (Exception $e) {
            $this->showErrorNotification(trans('auth.email_confirm_send_error'));

            return redirect('/register/awaiting');
        }

        $this->showSuccessNotification(trans('auth.email_confirm_resent'));

        return redirect('/register/confirm');
    }
}


================================================
FILE: app/Access/Controllers/ForgotPasswordController.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Activity\ActivityType;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Sleep;

class ForgotPasswordController extends Controller
{
    public function __construct()
    {
        $this->middleware('guest');
        $this->middleware('guard:standard');
    }

    /**
     * Display the form to request a password reset link.
     */
    public function showLinkRequestForm()
    {
        return view('auth.passwords.email');
    }

    /**
     * Send a reset link to the given user.
     */
    public function sendResetLinkEmail(Request $request)
    {
        $this->validate($request, [
            'email' => ['required', 'email'],
        ]);

        // Add random pause to the response to help avoid time-base sniffing
        // of valid resets via slower email send handling.
        Sleep::for(random_int(1000, 3000))->milliseconds();

        // We will send the password reset link to this user. Once we have attempted
        // to send the link, we will examine the response then see the message we
        // need to show to the user. Finally, we'll send out a proper response.
        $response = Password::broker()->sendResetLink(
            $request->only('email')
        );

        if ($response === Password::RESET_LINK_SENT) {
            $this->logActivity(ActivityType::AUTH_PASSWORD_RESET, $request->get('email'));
        }

        if (in_array($response, [Password::RESET_LINK_SENT, Password::INVALID_USER, Password::RESET_THROTTLED])) {
            $message = trans('auth.reset_password_sent', ['email' => $request->get('email')]);
            $this->showSuccessNotification($message);

            return redirect('/password/email')->with('status', trans($response));
        }

        // If an error was returned by the password broker, we will get this message
        // translated so we can notify a user of the problem. We'll redirect back
        // to where the users came from so they can attempt this process again.
        return redirect('/password/email')->withErrors(
            ['email' => trans($response)]
        );
    }
}


================================================
FILE: app/Access/Controllers/HandlesPartialLogins.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Access\LoginService;
use BookStack\Exceptions\NotFoundException;
use BookStack\Users\Models\User;

trait HandlesPartialLogins
{
    /**
     * @throws NotFoundException
     */
    protected function currentOrLastAttemptedUser(): User
    {
        $loginService = app()->make(LoginService::class);
        $user = auth()->user() ?? $loginService->getLastLoginAttemptUser();

        if (!$user) {
            throw new NotFoundException(trans('errors.login_user_not_found'));
        }

        return $user;
    }
}


================================================
FILE: app/Access/Controllers/LoginController.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Access\LoginService;
use BookStack\Access\SocialDriverManager;
use BookStack\Exceptions\LoginAttemptEmailNeededException;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Facades\Activity;
use BookStack\Http\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

class LoginController extends Controller
{
    use ThrottlesLogins;

    public function __construct(
        protected SocialDriverManager $socialDriverManager,
        protected LoginService $loginService,
    ) {
        $this->middleware('guest', ['only' => ['getLogin', 'login']]);
        $this->middleware('guard:standard,ldap', ['only' => ['login']]);
        $this->middleware('guard:standard,ldap,oidc', ['only' => ['logout']]);
    }

    /**
     * Show the application login form.
     */
    public function getLogin(Request $request)
    {
        $socialDrivers = $this->socialDriverManager->getActive();
        $authMethod = config('auth.method');
        $preventInitiation = $request->get('prevent_auto_init') === 'true';

        if ($request->has('email')) {
            session()->flashInput([
                'email'    => $request->get('email'),
                'password' => (config('app.env') === 'demo') ? $request->get('password', '') : '',
            ]);
        }

        // Store the previous location for redirect after login
        $this->updateIntendedFromPrevious();

        if (!$preventInitiation && $this->loginService->shouldAutoInitiate()) {
            return view('auth.login-initiate', [
                'authMethod'    => $authMethod,
            ]);
        }

        return view('auth.login', [
            'socialDrivers' => $socialDrivers,
            'authMethod'    => $authMethod,
        ]);
    }

    /**
     * Handle a login request to the application.
     */
    public function login(Request $request)
    {
        $this->validateLogin($request);
        $username = $request->get($this->username());

        // Check login throttling attempts to see if they've gone over the limit
        if ($this->hasTooManyLoginAttempts($request)) {
            Activity::logFailedLogin($username);
            return $this->sendLockoutResponse($request);
        }

        try {
            if ($this->attemptLogin($request)) {
                return $this->sendLoginResponse($request);
            }
        } catch (LoginAttemptException $exception) {
            Activity::logFailedLogin($username);

            return $this->sendLoginAttemptExceptionResponse($exception, $request);
        }

        // On unsuccessful login attempt, Increment login attempts for throttling and log failed login.
        $this->incrementLoginAttempts($request);
        Activity::logFailedLogin($username);

        // Throw validation failure for failed login
        throw ValidationException::withMessages([
            $this->username() => [trans('auth.failed')],
        ])->redirectTo('/login');
    }

    /**
     * Logout user and perform subsequent redirect.
     */
    public function logout()
    {
        return redirect($this->loginService->logout());
    }

    /**
     * Get the expected username input based upon the current auth method.
     */
    protected function username(): string
    {
        return config('auth.method') === 'standard' ? 'email' : 'username';
    }

    /**
     * Get the needed authorization credentials from the request.
     */
    protected function credentials(Request $request): array
    {
        return $request->only('username', 'email', 'password');
    }

    /**
     * Send the response after the user was authenticated.
     * @return RedirectResponse
     */
    protected function sendLoginResponse(Request $request)
    {
        $request->session()->regenerate();
        $this->clearLoginAttempts($request);

        return redirect()->intended('/');
    }

    /**
     * Attempt to log the user into the application.
     */
    protected function attemptLogin(Request $request): bool
    {
        return $this->loginService->attempt(
            $this->credentials($request),
            auth()->getDefaultDriver(),
            $request->filled('remember')
        );
    }


    /**
     * Validate the user login request.
     * @throws ValidationException
     */
    protected function validateLogin(Request $request): void
    {
        $rules = ['password' => ['required', 'string']];
        $authMethod = config('auth.method');

        if ($authMethod === 'standard') {
            $rules['email'] = ['required', 'email'];
        }

        if ($authMethod === 'ldap') {
            $rules['username'] = ['required', 'string'];
            $rules['email'] = ['email'];
        }

        $request->validate($rules);
    }

    /**
     * Send a response when a login attempt exception occurs.
     */
    protected function sendLoginAttemptExceptionResponse(LoginAttemptException $exception, Request $request)
    {
        if ($exception instanceof LoginAttemptEmailNeededException) {
            $request->flash();
            session()->flash('request-email', true);
        }

        if ($message = $exception->getMessage()) {
            $this->showWarningNotification($message);
        }

        return redirect('/login');
    }

    /**
     * Update the intended URL location from their previous URL.
     * Ignores if not from the current app instance or if from certain
     * login or authentication routes.
     */
    protected function updateIntendedFromPrevious(): void
    {
        // Store the previous location for redirect after login
        $previous = url()->previous('');
        $isPreviousFromInstance = str_starts_with($previous, url('/'));
        if (!$previous || !setting('app-public') || !$isPreviousFromInstance) {
            return;
        }

        $ignorePrefixList = [
            '/login',
            '/mfa',
        ];

        foreach ($ignorePrefixList as $ignorePrefix) {
            if (str_starts_with($previous, url($ignorePrefix))) {
                return;
            }
        }

        redirect()->setIntendedUrl($previous);
    }
}


================================================
FILE: app/Access/Controllers/MfaBackupCodesController.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Access\LoginService;
use BookStack\Access\Mfa\BackupCodeService;
use BookStack\Access\Mfa\MfaSession;
use BookStack\Access\Mfa\MfaValue;
use BookStack\Activity\ActivityType;
use BookStack\Exceptions\NotFoundException;
use BookStack\Http\Controller;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

class MfaBackupCodesController extends Controller
{
    use HandlesPartialLogins;

    protected const SETUP_SECRET_SESSION_KEY = 'mfa-setup-backup-codes';

    /**
     * Show a view that generates and displays backup codes.
     */
    public function generate(BackupCodeService $codeService)
    {
        $codes = $codeService->generateNewSet();
        session()->put(self::SETUP_SECRET_SESSION_KEY, encrypt($codes));

        $downloadUrl = 'data:application/octet-stream;base64,' . base64_encode(implode("\n\n", $codes));

        $this->setPageTitle(trans('auth.mfa_gen_backup_codes_title'));

        return view('mfa.backup-codes-generate', [
            'codes'       => $codes,
            'downloadUrl' => $downloadUrl,
        ]);
    }

    /**
     * Confirm the setup of backup codes, storing them against the user.
     *
     * @throws Exception
     */
    public function confirm()
    {
        if (!session()->has(self::SETUP_SECRET_SESSION_KEY)) {
            return response('No generated codes found in the session', 500);
        }

        $codes = decrypt(session()->pull(self::SETUP_SECRET_SESSION_KEY));
        MfaValue::upsertWithValue($this->currentOrLastAttemptedUser(), MfaValue::METHOD_BACKUP_CODES, json_encode($codes));

        $this->logActivity(ActivityType::MFA_SETUP_METHOD, 'backup-codes');

        if (!auth()->check()) {
            $this->showSuccessNotification(trans('auth.mfa_setup_login_notification'));

            return redirect('/login');
        }

        return redirect('/mfa/setup');
    }

    /**
     * Verify the MFA method submission on check.
     *
     * @throws NotFoundException
     * @throws ValidationException
     */
    public function verify(Request $request, BackupCodeService $codeService, MfaSession $mfaSession, LoginService $loginService)
    {
        $user = $this->currentOrLastAttemptedUser();
        $codes = MfaValue::getValueForUser($user, MfaValue::METHOD_BACKUP_CODES) ?? '[]';

        $this->validate($request, [
            'code' => [
                'required', 'max:12', 'min:8',
                function ($attribute, $value, $fail) use ($codeService, $codes) {
                    if (!$codeService->inputCodeExistsInSet($value, $codes)) {
                        $fail(trans('validation.backup_codes'));
                    }
                },
            ],
        ]);

        $updatedCodes = $codeService->removeInputCodeFromSet($request->get('code'), $codes);
        MfaValue::upsertWithValue($user, MfaValue::METHOD_BACKUP_CODES, $updatedCodes);

        $mfaSession->markVerifiedForUser($user);
        $loginService->reattemptLoginFor($user);

        if ($codeService->countCodesInSet($updatedCodes) < 5) {
            $this->showWarningNotification(trans('auth.mfa_backup_codes_usage_limit_warning'));
        }

        return redirect()->intended();
    }
}


================================================
FILE: app/Access/Controllers/MfaController.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Access\Mfa\MfaValue;
use BookStack\Activity\ActivityType;
use BookStack\Http\Controller;
use Illuminate\Http\Request;

class MfaController extends Controller
{
    use HandlesPartialLogins;

    /**
     * Show the view to setup MFA for the current user.
     */
    public function setup()
    {
        $userMethods = $this->currentOrLastAttemptedUser()
            ->mfaValues()
            ->get(['id', 'method'])
            ->groupBy('method');

        $this->setPageTitle(trans('auth.mfa_setup'));

        return view('mfa.setup', [
            'userMethods' => $userMethods,
        ]);
    }

    /**
     * Remove an MFA method for the current user.
     *
     * @throws \Exception
     */
    public function remove(string $method)
    {
        if (in_array($method, MfaValue::allMethods())) {
            $value = user()->mfaValues()->where('method', '=', $method)->first();
            if ($value) {
                $value->delete();
                $this->logActivity(ActivityType::MFA_REMOVE_METHOD, $method);
            }
        }

        return redirect('/mfa/setup');
    }

    /**
     * Show the page to start an MFA verification.
     */
    public function verify(Request $request)
    {
        $desiredMethod = $request->get('method');
        $userMethods = $this->currentOrLastAttemptedUser()
            ->mfaValues()
            ->get(['id', 'method'])
            ->groupBy('method');

        // Basic search for the default option for a user.
        // (Prioritises totp over backup codes)
        $method = $userMethods->has($desiredMethod) ? $desiredMethod : $userMethods->keys()->sort()->reverse()->first();
        $otherMethods = $userMethods->keys()->filter(function ($userMethod) use ($method) {
            return $method !== $userMethod;
        })->all();

        return view('mfa.verify', [
            'userMethods'  => $userMethods,
            'method'       => $method,
            'otherMethods' => $otherMethods,
        ]);
    }
}


================================================
FILE: app/Access/Controllers/MfaTotpController.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Access\LoginService;
use BookStack\Access\Mfa\MfaSession;
use BookStack\Access\Mfa\MfaValue;
use BookStack\Access\Mfa\TotpService;
use BookStack\Access\Mfa\TotpValidationRule;
use BookStack\Activity\ActivityType;
use BookStack\Exceptions\NotFoundException;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

class MfaTotpController extends Controller
{
    use HandlesPartialLogins;

    protected const SETUP_SECRET_SESSION_KEY = 'mfa-setup-totp-secret';

    public function __construct(
        protected TotpService $totp
    ) {
    }

    /**
     * Show a view that generates and displays a TOTP QR code.
     */
    public function generate()
    {
        if (session()->has(static::SETUP_SECRET_SESSION_KEY)) {
            $totpSecret = decrypt(session()->get(static::SETUP_SECRET_SESSION_KEY));
        } else {
            $totpSecret = $this->totp->generateSecret();
            session()->put(static::SETUP_SECRET_SESSION_KEY, encrypt($totpSecret));
        }

        $qrCodeUrl = $this->totp->generateUrl($totpSecret, $this->currentOrLastAttemptedUser());
        $svg = $this->totp->generateQrCodeSvg($qrCodeUrl);

        $this->setPageTitle(trans('auth.mfa_gen_totp_title'));

        return view('mfa.totp-generate', [
            'url' => $qrCodeUrl,
            'svg' => $svg,
        ]);
    }

    /**
     * Confirm the setup of TOTP and save the auth method secret
     * against the current user.
     *
     * @throws ValidationException
     * @throws NotFoundException
     */
    public function confirm(Request $request)
    {
        $totpSecret = decrypt(session()->get(static::SETUP_SECRET_SESSION_KEY));
        $this->validate($request, [
            'code' => [
                'required',
                'max:12', 'min:4',
                new TotpValidationRule($totpSecret, $this->totp),
            ],
        ]);

        MfaValue::upsertWithValue($this->currentOrLastAttemptedUser(), MfaValue::METHOD_TOTP, $totpSecret);
        session()->remove(static::SETUP_SECRET_SESSION_KEY);
        $this->logActivity(ActivityType::MFA_SETUP_METHOD, 'totp');

        if (!auth()->check()) {
            $this->showSuccessNotification(trans('auth.mfa_setup_login_notification'));

            return redirect('/login');
        }

        return redirect('/mfa/setup');
    }

    /**
     * Verify the MFA method submission on check.
     *
     * @throws NotFoundException
     */
    public function verify(Request $request, LoginService $loginService, MfaSession $mfaSession)
    {
        $user = $this->currentOrLastAttemptedUser();
        $totpSecret = MfaValue::getValueForUser($user, MfaValue::METHOD_TOTP);

        $this->validate($request, [
            'code' => [
                'required',
                'max:12', 'min:4',
                new TotpValidationRule($totpSecret, $this->totp),
            ],
        ]);

        $mfaSession->markVerifiedForUser($user);
        $loginService->reattemptLoginFor($user);

        return redirect()->intended();
    }
}


================================================
FILE: app/Access/Controllers/OidcController.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Access\Oidc\OidcException;
use BookStack\Access\Oidc\OidcService;
use BookStack\Http\Controller;
use Illuminate\Http\Request;

class OidcController extends Controller
{
    public function __construct(
        protected OidcService $oidcService
    ) {
        $this->middleware('guard:oidc');
    }

    /**
     * Start the authorization login flow via OIDC.
     */
    public function login()
    {
        try {
            $loginDetails = $this->oidcService->login();
        } catch (OidcException $exception) {
            $this->showErrorNotification($exception->getMessage());

            return redirect('/login');
        }

        session()->put('oidc_state', time() . ':' . $loginDetails['state']);

        return redirect($loginDetails['url']);
    }

    /**
     * Authorization flow redirect callback.
     * Processes authorization response from the OIDC Authorization Server.
     */
    public function callback(Request $request)
    {
        $responseState = $request->query('state');
        $splitState =  explode(':', session()->pull('oidc_state', ':'), 2);
        if (count($splitState) !== 2) {
            $splitState = [null, null];
        }

        [$storedStateTime, $storedState] = $splitState;
        $threeMinutesAgo = time() - 3 * 60;

        if (!$storedState || $storedState !== $responseState || intval($storedStateTime) < $threeMinutesAgo) {
            $this->showErrorNotification(trans('errors.oidc_fail_authed', ['system' => config('oidc.name')]));

            return redirect('/login');
        }

        try {
            $this->oidcService->processAuthorizeResponse($request->query('code'));
        } catch (OidcException $oidcException) {
            $this->showErrorNotification($oidcException->getMessage());

            return redirect('/login');
        }

        return redirect()->intended();
    }

    /**
     * Log the user out, then start the OIDC RP-initiated logout process.
     */
    public function logout()
    {
        return redirect($this->oidcService->logout());
    }
}


================================================
FILE: app/Access/Controllers/RegisterController.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Access\LoginService;
use BookStack\Access\RegistrationService;
use BookStack\Access\SocialDriverManager;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Http\Controller;
use Illuminate\Contracts\Validation\Validator as ValidatorContract;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Password;

class RegisterController extends Controller
{
    public function __construct(
        protected SocialDriverManager $socialDriverManager,
        protected RegistrationService $registrationService,
        protected LoginService $loginService
    ) {
        $this->middleware('guest');
        $this->middleware('guard:standard');
    }

    /**
     * Show the application registration form.
     *
     * @throws UserRegistrationException
     */
    public function getRegister()
    {
        $this->registrationService->ensureRegistrationAllowed();
        $socialDrivers = $this->socialDriverManager->getActive();

        return view('auth.register', [
            'socialDrivers' => $socialDrivers,
        ]);
    }

    /**
     * Handle a registration request for the application.
     *
     * @throws UserRegistrationException
     * @throws StoppedAuthenticationException
     */
    public function postRegister(Request $request)
    {
        $this->registrationService->ensureRegistrationAllowed();
        $this->validator($request->all())->validate();
        $userData = $request->all();

        try {
            $user = $this->registrationService->registerUser($userData);
            $this->loginService->login($user, auth()->getDefaultDriver());
        } catch (UserRegistrationException $exception) {
            if ($exception->getMessage()) {
                $this->showErrorNotification($exception->getMessage());
            }

            return redirect($exception->redirectLocation);
        }

        $this->showSuccessNotification(trans('auth.register_success'));

        return redirect('/');
    }

    /**
     * Get a validator for an incoming registration request.
     */
    protected function validator(array $data): ValidatorContract
    {
        return Validator::make($data, [
            'name'     => ['required', 'min:2', 'max:100'],
            'email'    => ['required', 'email', 'max:255', 'unique:users'],
            'password' => ['required', Password::default()],
            // Basic honey for bots that must not be filled in
            'username' => ['prohibited'],
        ]);
    }
}


================================================
FILE: app/Access/Controllers/ResetPasswordController.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Access\LoginService;
use BookStack\Activity\ActivityType;
use BookStack\Http\Controller;
use BookStack\Users\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password as PasswordRule;

class ResetPasswordController extends Controller
{
    public function __construct(
        protected LoginService $loginService
    ) {
        $this->middleware('guest');
        $this->middleware('guard:standard');
    }

    /**
     * Display the password reset view for the given token.
     * If no token is present, display the link request form.
     */
    public function showResetForm(Request $request)
    {
        $token = $request->route()->parameter('token');

        return view('auth.passwords.reset')->with(
            ['token' => $token, 'email' => $request->email]
        );
    }

    /**
     * Reset the given user's password.
     */
    public function reset(Request $request)
    {
        $request->validate([
            'token' => 'required',
            'email' => 'required|email',
            'password' => ['required', 'confirmed', PasswordRule::defaults()],
        ]);

        // Here we will attempt to reset the user's password. If it is successful we
        // will update the password on an actual user model and persist it to the
        // database. Otherwise we will parse the error and return the response.
        $credentials = $request->only('email', 'password', 'password_confirmation', 'token');
        $response = Password::broker()->reset($credentials, function (User $user, string $password) {
            $user->password = Hash::make($password);
            $user->setRememberToken(Str::random(60));
            $user->save();

            $this->loginService->login($user, auth()->getDefaultDriver());
        });

        // If the password was successfully reset, we will redirect the user back to
        // the application's home authenticated view. If there is an error we can
        // redirect them back to where they came from with their error message.
        return $response === Password::PASSWORD_RESET
            ? $this->sendResetResponse()
            : $this->sendResetFailedResponse($request, $response, $request->get('token'));
    }

    /**
     * Get the response for a successful password reset.
     */
    protected function sendResetResponse(): RedirectResponse
    {
        $this->showSuccessNotification(trans('auth.reset_password_success'));
        $this->logActivity(ActivityType::AUTH_PASSWORD_RESET_UPDATE, user());

        return redirect('/');
    }

    /**
     * Get the response for a failed password reset.
     */
    protected function sendResetFailedResponse(Request $request, string $response, string $token): RedirectResponse
    {
        // We show invalid users as invalid tokens as to not leak what
        // users may exist in the system.
        if ($response === Password::INVALID_USER) {
            $response = Password::INVALID_TOKEN;
        }

        return redirect("/password/reset/{$token}")
            ->withInput($request->only('email'))
            ->withErrors(['email' => trans($response)]);
    }
}


================================================
FILE: app/Access/Controllers/Saml2Controller.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Access\Saml2Service;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class Saml2Controller extends Controller
{
    public function __construct(
        protected Saml2Service $samlService
    ) {
        $this->middleware('guard:saml2');
    }

    /**
     * Start the login flow via SAML2.
     */
    public function login()
    {
        $loginDetails = $this->samlService->login();
        session()->flash('saml2_request_id', $loginDetails['id']);

        return redirect($loginDetails['url']);
    }

    /**
     * Start the logout flow via SAML2.
     */
    public function logout()
    {
        $user = user();
        if ($user->isGuest()) {
            return redirect('/login');
        }

        $logoutDetails = $this->samlService->logout($user);

        if ($logoutDetails['id']) {
            session()->flash('saml2_logout_request_id', $logoutDetails['id']);
        }

        return redirect($logoutDetails['url']);
    }

    /*
     * Get the metadata for this SAML2 service provider.
     */
    public function metadata()
    {
        $metaData = $this->samlService->metadata();

        return response()->make($metaData, 200, [
            'Content-Type' => 'text/xml',
        ]);
    }

    /**
     * Single logout service.
     * Handle logout requests and responses.
     */
    public function sls()
    {
        $requestId = session()->pull('saml2_logout_request_id', null);
        $redirect = $this->samlService->processSlsResponse($requestId);

        return redirect($redirect);
    }

    /**
     * Assertion Consumer Service start URL. Takes the SAMLResponse from the IDP.
     * Due to being an external POST request, we likely won't have context of the
     * current user session due to lax cookies. To work around this we store the
     * SAMLResponse data and redirect to the processAcs endpoint for the actual
     * processing of the request with proper context of the user session.
     */
    public function startAcs(Request $request)
    {
        $samlResponse = $request->get('SAMLResponse', null);

        if (empty($samlResponse)) {
            $this->showErrorNotification(trans('errors.saml_fail_authed', ['system' => config('saml2.name')]));

            return redirect('/login');
        }

        $acsId = Str::random(16);
        $cacheKey = 'saml2_acs:' . $acsId;
        cache()->set($cacheKey, encrypt($samlResponse), 10);

        return redirect()->guest('/saml2/acs?id=' . $acsId);
    }

    /**
     * Assertion Consumer Service process endpoint.
     * Processes the SAML response from the IDP with context of the current session.
     * Takes the SAML request from the cache, added by the startAcs method above.
     */
    public function processAcs(Request $request)
    {
        $acsId = $request->get('id', null);
        $cacheKey = 'saml2_acs:' . $acsId;
        $samlResponse = null;

        try {
            $samlResponse = decrypt(cache()->pull($cacheKey));
        } catch (\Exception $exception) {
        }
        $requestId = session()->pull('saml2_request_id', null);

        if (empty($acsId) || empty($samlResponse)) {
            $this->showErrorNotification(trans('errors.saml_fail_authed', ['system' => config('saml2.name')]));

            return redirect('/login');
        }

        $user = $this->samlService->processAcsResponse($requestId, $samlResponse);
        if (is_null($user)) {
            $this->showErrorNotification(trans('errors.saml_fail_authed', ['system' => config('saml2.name')]));

            return redirect('/login');
        }

        return redirect()->intended();
    }
}


================================================
FILE: app/Access/Controllers/SocialController.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Access\LoginService;
use BookStack\Access\RegistrationService;
use BookStack\Access\SocialAuthService;
use BookStack\Exceptions\SocialDriverNotConfigured;
use BookStack\Exceptions\SocialSignInAccountNotUsed;
use BookStack\Exceptions\SocialSignInException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Laravel\Socialite\Contracts\User as SocialUser;

class SocialController extends Controller
{
    public function __construct(
        protected SocialAuthService $socialAuthService,
        protected RegistrationService $registrationService,
        protected LoginService $loginService,
    ) {
        $this->middleware('guest')->only(['register']);
    }

    /**
     * Redirect to the relevant social site.
     *
     * @throws SocialDriverNotConfigured
     */
    public function login(string $socialDriver)
    {
        session()->put('social-callback', 'login');

        return $this->socialAuthService->startLogIn($socialDriver);
    }

    /**
     * Redirect to the social site for authentication intended to register.
     *
     * @throws SocialDriverNotConfigured
     * @throws UserRegistrationException
     */
    public function register(string $socialDriver)
    {
        $this->registrationService->ensureRegistrationAllowed();
        session()->put('social-callback', 'register');

        return $this->socialAuthService->startRegister($socialDriver);
    }

    /**
     * The callback for social login services.
     *
     * @throws SocialSignInException
     * @throws SocialDriverNotConfigured
     * @throws UserRegistrationException
     */
    public function callback(Request $request, string $socialDriver)
    {
        if (!session()->has('social-callback')) {
            throw new SocialSignInException(trans('errors.social_no_action_defined'), '/login');
        }

        // Check request for error information
        if ($request->has('error') && $request->has('error_description')) {
            throw new SocialSignInException(trans('errors.social_login_bad_response', [
                'socialAccount' => $socialDriver,
                'error'         => $request->get('error_description'),
            ]), '/login');
        }

        $action = session()->pull('social-callback');

        // Attempt login or fall-back to register if allowed.
        $socialUser = $this->socialAuthService->getSocialUser($socialDriver);
        if ($action === 'login') {
            try {
                return $this->socialAuthService->handleLoginCallback($socialDriver, $socialUser);
            } catch (SocialSignInAccountNotUsed $exception) {
                if ($this->socialAuthService->drivers()->isAutoRegisterEnabled($socialDriver)) {
                    return $this->socialRegisterCallback($socialDriver, $socialUser);
                }

                throw $exception;
            }
        }

        if ($action === 'register') {
            return $this->socialRegisterCallback($socialDriver, $socialUser);
        }

        return redirect('/');
    }

    /**
     * Detach a social account from a user.
     */
    public function detach(string $socialDriver)
    {
        $this->socialAuthService->detachSocialAccount($socialDriver);
        session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => Str::title($socialDriver)]));

        return redirect('/my-account/auth#social-accounts');
    }

    /**
     * Register a new user after a registration callback.
     *
     * @throws UserRegistrationException
     */
    protected function socialRegisterCallback(string $socialDriver, SocialUser $socialUser)
    {
        $socialUser = $this->socialAuthService->handleRegistrationCallback($socialDriver, $socialUser);
        $socialAccount = $this->socialAuthService->newSocialAccount($socialDriver, $socialUser);
        $emailVerified = $this->socialAuthService->drivers()->isAutoConfirmEmailEnabled($socialDriver);

        // Create an array of the user data to create a new user instance
        $userData = [
            'name'     => $socialUser->getName(),
            'email'    => $socialUser->getEmail(),
            'password' => Str::random(32),
        ];

        // Take name from email address if empty
        if (!$userData['name']) {
            $userData['name'] = explode('@', $userData['email'])[0];
        }

        $user = $this->registrationService->registerUser($userData, $socialAccount, $emailVerified);
        $this->showSuccessNotification(trans('auth.register_success'));
        $this->loginService->login($user, $socialDriver);

        return redirect('/');
    }
}


================================================
FILE: app/Access/Controllers/ThrottlesLogins.php
================================================
<?php

namespace BookStack\Access\Controllers;

use Illuminate\Cache\RateLimiter;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

trait ThrottlesLogins
{
    /**
     * Determine if the user has too many failed login attempts.
     */
    protected function hasTooManyLoginAttempts(Request $request): bool
    {
        return $this->limiter()->tooManyAttempts(
            $this->throttleKey($request),
            $this->maxAttempts()
        );
    }

    /**
     * Increment the login attempts for the user.
     */
    protected function incrementLoginAttempts(Request $request): void
    {
        $this->limiter()->hit(
            $this->throttleKey($request),
            $this->decayMinutes() * 60
        );
    }

    /**
     * Redirect the user after determining they are locked out.
     * @throws ValidationException
     */
    protected function sendLockoutResponse(Request $request): \Symfony\Component\HttpFoundation\Response
    {
        $seconds = $this->limiter()->availableIn(
            $this->throttleKey($request)
        );

        throw ValidationException::withMessages([
            $this->username() => [trans('auth.throttle', [
                'seconds' => $seconds,
                'minutes' => ceil($seconds / 60),
            ])],
        ])->status(Response::HTTP_TOO_MANY_REQUESTS);
    }

    /**
     * Clear the login locks for the given user credentials.
     */
    protected function clearLoginAttempts(Request $request): void
    {
        $this->limiter()->clear($this->throttleKey($request));
    }

    /**
     * Get the throttle key for the given request.
     */
    protected function throttleKey(Request $request): string
    {
        return Str::transliterate(Str::lower($request->input($this->username())) . '|' . $request->ip());
    }

    /**
     * Get the rate limiter instance.
     */
    protected function limiter(): RateLimiter
    {
        return app()->make(RateLimiter::class);
    }

    /**
     * Get the maximum number of attempts to allow.
     */
    public function maxAttempts(): int
    {
        return 5;
    }

    /**
     * Get the number of minutes to throttle for.
     */
    public function decayMinutes(): int
    {
        return 1;
    }
}


================================================
FILE: app/Access/Controllers/UserInviteController.php
================================================
<?php

namespace BookStack\Access\Controllers;

use BookStack\Access\UserInviteService;
use BookStack\Exceptions\UserTokenExpiredException;
use BookStack\Exceptions\UserTokenNotFoundException;
use BookStack\Http\Controller;
use BookStack\Users\UserRepo;
use Exception;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;

class UserInviteController extends Controller
{
    protected UserInviteService $inviteService;
    protected UserRepo $userRepo;

    /**
     * Create a new controller instance.
     */
    public function __construct(UserInviteService $inviteService, UserRepo $userRepo)
    {
        $this->middleware('guest');
        $this->middleware('guard:standard');

        $this->inviteService = $inviteService;
        $this->userRepo = $userRepo;
    }

    /**
     * Show the page for the user to set the password for their account.
     *
     * @throws Exception
     */
    public function showSetPassword(string $token)
    {
        try {
            $this->inviteService->checkTokenAndGetUserId($token);
        } catch (Exception $exception) {
            return $this->handleTokenException($exception);
        }

        return view('auth.invite-set-password', [
            'token' => $token,
        ]);
    }

    /**
     * Sets the password for an invited user and then grants them access.
     *
     * @throws Exception
     */
    public function setPassword(Request $request, string $token)
    {
        $this->validate($request, [
            'password' => ['required', Password::default()],
        ]);

        try {
            $userId = $this->inviteService->checkTokenAndGetUserId($token);
        } catch (Exception $exception) {
            return $this->handleTokenException($exception);
        }

        $user = $this->userRepo->getById($userId);
        $user->password = Hash::make($request->get('password'));
        $user->email_confirmed = true;
        $user->save();

        $this->inviteService->deleteByUser($user);
        $this->showSuccessNotification(trans('auth.user_invite_success_login', ['appName' => setting('app-name')]));

        return redirect('/login');
    }

    /**
     * Check and validate the exception thrown when checking an invite token.
     *
     * @throws Exception
     *
     * @return RedirectResponse|Redirector
     */
    protected function handleTokenException(Exception $exception)
    {
        if ($exception instanceof UserTokenNotFoundException) {
            return redirect('/');
        }

        if ($exception instanceof UserTokenExpiredException) {
            $this->showErrorNotification(trans('errors.invite_token_expired'));

            return redirect('/password/email');
        }

        throw $exception;
    }
}


================================================
FILE: app/Access/EmailConfirmationService.php
================================================
<?php

namespace BookStack\Access;

use BookStack\Access\Notifications\ConfirmEmailNotification;
use BookStack\Exceptions\ConfirmationEmailException;
use BookStack\Users\Models\User;

class EmailConfirmationService extends UserTokenService
{
    protected string $tokenTable = 'email_confirmations';
    protected int $expiryTime = 24;

    /**
     * Create new confirmation for a user,
     * Also removes any existing old ones.
     *
     * @throws ConfirmationEmailException
     */
    public function sendConfirmation(User $user): void
    {
        if ($user->email_confirmed) {
            throw new ConfirmationEmailException(trans('errors.email_already_confirmed'), '/login');
        }

        $this->deleteByUser($user);
        $token = $this->createTokenForUser($user);

        $user->notify(new ConfirmEmailNotification($token));
    }

    /**
     * Check if confirmation is required in this instance.
     */
    public function confirmationRequired(): bool
    {
        return setting('registration-confirmation')
            || setting('registration-restrict');
    }
}


================================================
FILE: app/Access/ExternalBaseUserProvider.php
================================================
<?php

namespace BookStack\Access;

use BookStack\Users\Models\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;

class ExternalBaseUserProvider implements UserProvider
{
    /**
     * Retrieve a user by their unique identifier.
     */
    public function retrieveById(mixed $identifier): ?Authenticatable
    {
        return User::query()->find($identifier);
    }

    /**
     * Retrieve a user by their unique identifier and "remember me" token.
     *
     * @param string $token
     */
    public function retrieveByToken(mixed $identifier, $token): null
    {
        return null;
    }

    /**
     * Update the "remember me" token for the given user in storage.
     *
     * @param Authenticatable $user
     * @param string          $token
     *
     * @return void
     */
    public function updateRememberToken(Authenticatable $user, $token)
    {
        //
    }

    /**
     * Retrieve a user by the given credentials.
     */
    public function retrieveByCredentials(array $credentials): ?Authenticatable
    {
        return User::query()
            ->where('external_auth_id', $credentials['external_auth_id'])
            ->first();
    }

    /**
     * Validate a user against the given credentials.
     */
    public function validateCredentials(Authenticatable $user, array $credentials): bool
    {
        // Should be done in the guard.
        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
    }
}


================================================
FILE: app/Access/GroupSyncService.php
================================================
<?php

namespace BookStack\Access;

use BookStack\Users\Models\Role;
use BookStack\Users\Models\User;
use Illuminate\Support\Collection;

class GroupSyncService
{
    /**
     * Check a role against an array of group names to see if it matches.
     * Checked against role 'external_auth_id' if set otherwise the name of the role.
     */
    protected function roleMatchesGroupNames(Role $role, array $groupNames): bool
    {
        if ($role->external_auth_id) {
            return $this->externalIdMatchesGroupNames($role->external_auth_id, $groupNames);
        }

        $roleName = str_replace(' ', '-', trim(strtolower($role->display_name)));

        return in_array($roleName, $groupNames);
    }

    /**
     * Check if the given external auth ID string matches one of the given group names.
     */
    protected function externalIdMatchesGroupNames(string $externalId, array $groupNames): bool
    {
        foreach ($this->parseRoleExternalAuthId($externalId) as $externalAuthId) {
            if (in_array($externalAuthId, $groupNames)) {
                return true;
            }
        }

        return false;
    }

    protected function parseRoleExternalAuthId(string $externalId): array
    {
        $inputIds = preg_split('/(?<!\\\),/', strtolower($externalId));
        $cleanIds = [];

        foreach ($inputIds as $inputId) {
            $cleanIds[] = str_replace('\,', ',', trim($inputId));
        }

        return $cleanIds;
    }

    /**
     * Match an array of group names to BookStack system roles.
     * Formats group names to be lower-case and hyphenated.
     */
    protected function matchGroupsToSystemsRoles(array $groupNames): Collection
    {
        foreach ($groupNames as $i => $groupName) {
            $groupNames[$i] = str_replace(' ', '-', trim(strtolower($groupName)));
        }

        $roles = Role::query()->get(['id', 'external_auth_id', 'display_name']);
        $matchedRoles = $roles->filter(function (Role $role) use ($groupNames) {
            return $this->roleMatchesGroupNames($role, $groupNames);
        });

        return $matchedRoles->pluck('id');
    }

    /**
     * Sync the groups to the user roles for the current user.
     */
    public function syncUserWithFoundGroups(User $user, array $userGroups, bool $detachExisting): void
    {
        // Get the ids for the roles from the names
        $groupsAsRoles = $this->matchGroupsToSystemsRoles($userGroups);

        // Sync groups
        if ($detachExisting) {
            $user->roles()->sync($groupsAsRoles);
            $user->attachDefaultRole();
        } else {
            $user->roles()->syncWithoutDetaching($groupsAsRoles);
        }
    }
}


================================================
FILE: app/Access/Guards/AsyncExternalBaseSessionGuard.php
================================================
<?php

namespace BookStack\Access\Guards;

/**
 * External Auth Session Guard.
 *
 * The login process for external auth (SAML2/OIDC) is async in nature, meaning it does not fit very well
 * into the default laravel 'Guard' auth flow. Instead, most of the logic is done via the relevant
 * controller and services. This class provides a safer, thin version of SessionGuard.
 */
class AsyncExternalBaseSessionGuard extends ExternalBaseSessionGuard
{
    /**
     * Validate a user's credentials.
     */
    public function validate(array $credentials = []): bool
    {
        return false;
    }

    /**
     * Attempt to authenticate a user using the given credentials.
     *
     * @param bool  $remember
     */
    public function attempt(array $credentials = [], $remember = false): bool
    {
        return false;
    }
}


================================================
FILE: app/Access/Guards/ExternalBaseSessionGuard.php
================================================
<?php

namespace BookStack\Access\Guards;

use BookStack\Access\RegistrationService;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Session\Session;

/**
 * Class BaseSessionGuard
 * A base implementation of a session guard. Is a copy of the default Laravel
 * guard with 'remember' functionality removed. Basic auth and event emission
 * has also been removed to keep this simple. Designed to be extended by external
 * Auth Guards.
 */
class ExternalBaseSessionGuard implements StatefulGuard
{
    use GuardHelpers;

    /**
     * The name of the Guard. Typically "session".
     *
     * Corresponds to guard name in authentication configuration.
     */
    protected readonly string $name;

    /**
     * The user we last attempted to retrieve.
     */
    protected Authenticatable|null $lastAttempted;

    /**
     * The session used by the guard.
     */
    protected Session $session;

    /**
     * Indicates if the logout method has been called.
     */
    protected bool $loggedOut = false;

    /**
     * Service to handle common registration actions.
     */
    protected RegistrationService $registrationService;

    /**
     * Create a new authentication guard.
     */
    public function __construct(string $name, UserProvider $provider, Session $session, RegistrationService $registrationService)
    {
        $this->name = $name;
        $this->session = $session;
        $this->provider = $provider;
        $this->registrationService = $registrationService;
    }

    /**
     * Get the currently authenticated user.
     */
    public function user(): Authenticatable|null
    {
        if ($this->loggedOut) {
            return null;
        }

        // If we've already retrieved the user for the current request we can just
        // return it back immediately. We do not want to fetch the user data on
        // every call to this method because that would be tremendously slow.
        if (!is_null($this->user)) {
            return $this->user;
        }

        $id = $this->session->get($this->getName());

        // First we will try to load the user using the
        // identifier in the session if one exists.
        if (!is_null($id)) {
            $this->user = $this->provider->retrieveById($id);
        }

        return $this->user;
    }

    /**
     * Get the ID for the currently authenticated user.
     */
    public function id(): int|null
    {
        if ($this->loggedOut) {
            return null;
        }

        return $this->user()
            ? $this->user()->getAuthIdentifier()
            : $this->session->get($this->getName());
    }

    /**
     * Log a user into the application without sessions or cookies.
     */
    public function once(array $credentials = []): bool
    {
        if ($this->validate($credentials)) {
            $this->setUser($this->lastAttempted);

            return true;
        }

        return false;
    }

    /**
     * Log the given user ID into the application without sessions or cookies.
     */
    public function onceUsingId($id): Authenticatable|false
    {
        if (!is_null($user = $this->provider->retrieveById($id))) {
            $this->setUser($user);

            return $user;
        }

        return false;
    }

    /**
     * Validate a user's credentials.
     */
    public function validate(array $credentials = []): bool
    {
        return false;
    }

    /**
     * Attempt to authenticate a user using the given credentials.
     * @param bool $remember
     */
    public function attempt(array $credentials = [], $remember = false): bool
    {
        return false;
    }

    /**
     * Log the given user ID into the application.
     * @param bool  $remember
     */
    public function loginUsingId(mixed $id, $remember = false): Authenticatable|false
    {
        // Always return false as to disable this method,
        // Logins should route through LoginService.
        return false;
    }

    /**
     * Log a user into the application.
     *
     * @param bool $remember
     */
    public function login(Authenticatable $user, $remember = false): void
    {
        $this->updateSession($user->getAuthIdentifier());

        $this->setUser($user);
    }

    /**
     * Update the session with the given ID.
     */
    protected function updateSession(string|int $id): void
    {
        $this->session->put($this->getName(), $id);

        $this->session->migrate(true);
    }

    /**
     * Log the user out of the application.
     */
    public function logout(): void
    {
        $this->clearUserDataFromStorage();

        // Now we will clear the users out of memory so they are no longer available
        // as the user is no longer considered as being signed into this
        // application and should not be available here.
        $this->user = null;

        $this->loggedOut = true;
    }

    /**
     * Remove the user data from the session and cookies.
     */
    protected function clearUserDataFromStorage(): void
    {
        $this->session->remove($this->getName());
    }

    /**
     * Get the last user we attempted to authenticate.
     */
    public function getLastAttempted(): Authenticatable
    {
        return $this->lastAttempted;
    }

    /**
     * Get a unique identifier for the auth session value.
     */
    public function getName(): string
    {
        return 'login_' . $this->name . '_' . sha1(static::class);
    }

    /**
     * Determine if the user was authenticated via "remember me" cookie.
     */
    public function viaRemember(): bool
    {
        return false;
    }

    /**
     * Return the currently cached user.
     */
    public function getUser(): Authenticatable|null
    {
        return $this->user;
    }

    /**
     * Set the current user.
     */
    public function setUser(Authenticatable $user): self
    {
        $this->user = $user;

        $this->loggedOut = false;

        return $this;
    }
}


================================================
FILE: app/Access/Guards/LdapSessionGuard.php
================================================
<?php

namespace BookStack\Access\Guards;

use BookStack\Access\LdapService;
use BookStack\Access\RegistrationService;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\LdapException;
use BookStack\Exceptions\LoginAttemptEmailNeededException;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Users\Models\User;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Session\Session;
use Illuminate\Support\Str;

class LdapSessionGuard extends ExternalBaseSessionGuard
{
    protected LdapService $ldapService;

    /**
     * LdapSessionGuard constructor.
     */
    public function __construct(
        $name,
        UserProvider $provider,
        Session $session,
        LdapService $ldapService,
        RegistrationService $registrationService
    ) {
        $this->ldapService = $ldapService;
        parent::__construct($name, $provider, $session, $registrationService);
    }

    /**
     * Validate a user's credentials.
     *
     * @throws LdapException
     */
    public function validate(array $credentials = []): bool
    {
        $userDetails = $this->ldapService->getUserDetails($credentials['username']);

        if (isset($userDetails['uid'])) {
            $this->lastAttempted = $this->provider->retrieveByCredentials([
                'external_auth_id' => $userDetails['uid'],
            ]);
        }

        return $this->ldapService->validateUserCredentials($userDetails, $credentials['password']);
    }

    /**
     * Attempt to authenticate a user using the given credentials.
     *
     * @param bool  $remember
     *
     * @throws LdapException
     * @throws LoginAttemptException
     * @throws JsonDebugException
     */
    public function attempt(array $credentials = [], $remember = false): bool
    {
        $username = $credentials['username'];
        $userDetails = $this->ldapService->getUserDetails($username);

        $user = null;
        if (isset($userDetails['uid'])) {
            $this->lastAttempted = $user = $this->provider->retrieveByCredentials([
                'external_auth_id' => $userDetails['uid'],
            ]);
        }

        if (!$this->ldapService->validateUserCredentials($userDetails, $credentials['password'])) {
            return false;
        }

        if (is_null($user)) {
            try {
                $user = $this->createNewFromLdapAndCreds($userDetails, $credentials);
            } catch (UserRegistrationException $exception) {
                throw new LoginAttemptException($exception->getMessage());
            }
        }

        // Sync LDAP groups if required
        if ($this->ldapService->shouldSyncGroups()) {
            $this->ldapService->syncGroups($user, $username);
        }

        // Attach avatar if non-existent
        if (!$user->avatar()->exists()) {
            $this->ldapService->saveAndAttachAvatar($user, $userDetails);
        }

        $this->login($user, $remember);

        return true;
    }

    /**
     * Create a new user from the given ldap credentials and login credentials.
     *
     * @throws LoginAttemptEmailNeededException
     * @throws LoginAttemptException
     * @throws UserRegistrationException
     */
    protected function createNewFromLdapAndCreds(array $ldapUserDetails, array $credentials): User
    {
        $email = trim($ldapUserDetails['email'] ?: ($credentials['email'] ?? ''));

        if (empty($email)) {
            throw new LoginAttemptEmailNeededException();
        }

        $details = [
            'name'             => $ldapUserDetails['name'],
            'email'            => $ldapUserDetails['email'] ?: $credentials['email'],
            'external_auth_id' => $ldapUserDetails['uid'],
            'password'         => Str::random(32),
        ];

        $user = $this->registrationService->registerUser($details, null, false);
        $this->ldapService->saveAndAttachAvatar($user, $ldapUserDetails);

        return $user;
    }
}


================================================
FILE: app/Access/Ldap.php
================================================
<?php

namespace BookStack\Access;

/**
 * Class Ldap
 * An object-orientated thin abstraction wrapper for common PHP LDAP functions.
 * Allows the standard LDAP functions to be mocked for testing.
 */
class Ldap
{
    /**
     * Connect to an LDAP server.
     *
     * @return resource|\LDAP\Connection|false
     */
    public function connect(string $hostName)
    {
        return ldap_connect($hostName);
    }

    /**
     * Set the value of an LDAP option for the given connection.
     *
     * @param resource|\LDAP\Connection|null $ldapConnection
     */
    public function setOption($ldapConnection, int $option, mixed $value): bool
    {
        return ldap_set_option($ldapConnection, $option, $value);
    }

    /**
     * Start TLS on the given LDAP connection.
     */
    public function startTls($ldapConnection): bool
    {
        return ldap_start_tls($ldapConnection);
    }

    /**
     * Set the version number for the given LDAP connection.
     *
     * @param resource|\LDAP\Connection $ldapConnection
     */
    public function setVersion($ldapConnection, int $version): bool
    {
        return $this->setOption($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, $version);
    }

    /**
     * Search LDAP tree using the provided filter.
     *
     * @param resource|\LDAP\Connection   $ldapConnection
     *
     * @return \LDAP\Result|array|false
     */
    public function search($ldapConnection, string $baseDn, string $filter, array $attributes = [])
    {
        return ldap_search($ldapConnection, $baseDn, $filter, $attributes);
    }

    /**
     * Read an entry from the LDAP tree.
     *
     * @param resource|\Ldap\Connection $ldapConnection
     *
     * @return \LDAP\Result|array|false
     */
    public function read($ldapConnection, string $baseDn, string $filter, array $attributes = [])
    {
        return ldap_read($ldapConnection, $baseDn, $filter, $attributes);
    }

    /**
     * Get entries from an LDAP search result.
     *
     * @param resource|\LDAP\Connection $ldapConnection
     * @param resource|\LDAP\Result $ldapSearchResult
     */
    public function getEntries($ldapConnection, $ldapSearchResult): array|false
    {
        return ldap_get_entries($ldapConnection, $ldapSearchResult);
    }

    /**
     * Search and get entries immediately.
     *
     * @param resource|\LDAP\Connection   $ldapConnection
     */
    public function searchAndGetEntries($ldapConnection, string $baseDn, string $filter, array $attributes = []): array|false
    {
        $search = $this->search($ldapConnection, $baseDn, $filter, $attributes);

        return $this->getEntries($ldapConnection, $search);
    }

    /**
     * Bind to LDAP directory.
     *
     * @param resource|\LDAP\Connection $ldapConnection
     */
    public function bind($ldapConnection, ?string $bindRdn = null, ?string $bindPassword = null): bool
    {
        return ldap_bind($ldapConnection, $bindRdn, $bindPassword);
    }

    /**
     * Explode an LDAP dn string into an array of components.
     */
    public function explodeDn(string $dn, int $withAttrib): array|false
    {
        return ldap_explode_dn($dn, $withAttrib);
    }

    /**
     * Escape a string for use in an LDAP filter.
     */
    public function escape(string $value, string $ignore = '', int $flags = 0): string
    {
        return ldap_escape($value, $ignore, $flags);
    }
}


================================================
FILE: app/Access/LdapService.php
================================================
<?php

namespace BookStack\Access;

use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\LdapException;
use BookStack\Uploads\UserAvatars;
use BookStack\Users\Models\User;
use ErrorException;
use Illuminate\Support\Facades\Log;

/**
 * Class LdapService
 * Handles any app-specific LDAP tasks.
 */
class LdapService
{
    /**
     * @var resource|\LDAP\Connection
     */
    protected $ldapConnection;

    protected array $config;
    protected bool $enabled;

    public function __construct(
        protected Ldap $ldap,
        protected UserAvatars $userAvatars,
        protected GroupSyncService $groupSyncService
    ) {
        $this->config = config('services.ldap');
        $this->enabled = config('auth.method') === 'ldap';
    }

    /**
     * Check if groups should be synced.
     */
    public function shouldSyncGroups(): bool
    {
        return $this->enabled && $this->config['user_to_groups'] !== false;
    }

    /**
     * Search for attributes for a specific user on the ldap.
     *
     * @throws LdapException
     */
    private function getUserWithAttributes(string $userName, array $attributes): ?array
    {
        $ldapConnection = $this->getConnection();
        $this->bindSystemUser($ldapConnection);

        // Clean attributes
        foreach ($attributes as $index => $attribute) {
            if (str_starts_with($attribute, 'BIN;')) {
                $attributes[$index] = substr($attribute, strlen('BIN;'));
            }
        }

        // Find user
        $userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
        $baseDn = $this->config['base_dn'];

        $followReferrals = $this->config['follow_referrals'] ? 1 : 0;
        $this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
        $users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, $attributes);
        if ($users['count'] === 0) {
            return null;
        }

        return $users[0];
    }

    /**
     * Build the user display name from the (potentially multiple) attributes defined by the configuration.
     */
    protected function getUserDisplayName(array $userDetails, array $displayNameAttrs, string $defaultValue): string
    {
        $displayNameParts = [];
        foreach ($displayNameAttrs as $dnAttr) {
            $dnComponent = $this->getUserResponseProperty($userDetails, $dnAttr, null);
            if ($dnComponent) {
                $displayNameParts[] = $dnComponent;
            }
        }

        if (empty($displayNameParts)) {
            return $defaultValue;
        }

        return implode(' ', $displayNameParts);
    }

    /**
     * Get the details of a user from LDAP using the given username.
     * User found via configurable user filter.
     *
     * @throws LdapException|JsonDebugException
     */
    public function getUserDetails(string $userName): ?array
    {
        $idAttr = $this->config['id_attribute'];
        $emailAttr = $this->config['email_attribute'];
        $displayNameAttrs = explode('|', $this->config['display_name_attribute']);
        $thumbnailAttr = $this->config['thumbnail_attribute'];

        $user = $this->getUserWithAttributes($userName, array_filter([
            'cn', 'dn', $idAttr, $emailAttr, ...$displayNameAttrs, $thumbnailAttr,
        ]));

        if (is_null($user)) {
            return null;
        }

        $nameDefault = $this->getUserResponseProperty($user, 'cn', null);
        if (is_null($nameDefault)) {
            $nameDefault = ldap_explode_dn($user['dn'], 1)[0] ?? $user['dn'];
        }

        $formatted = [
            'uid'   => $this->getUserResponseProperty($user, $idAttr, $user['dn']),
            'name'  => $this->getUserDisplayName($user, $displayNameAttrs, $nameDefault),
            'dn'    => $user['dn'],
            'email' => $this->getUserResponseProperty($user, $emailAttr, null),
            'avatar' => $thumbnailAttr ? $this->getUserResponseProperty($user, $thumbnailAttr, null) : null,
        ];

        if ($this->config['dump_user_details']) {
            throw new JsonDebugException([
                'details_from_ldap'        => $user,
                'details_bookstack_parsed' => $formatted,
            ]);
        }

        return $formatted;
    }

    /**
     * Get a property from an LDAP user response fetch.
     * Handles properties potentially being part of an array.
     * If the given key is prefixed with 'BIN;', that indicator will be stripped
     * from the key and any fetched values will be converted from binary to hex.
     */
    protected function getUserResponseProperty(array $userDetails, string $propertyKey, $defaultValue)
    {
        $isBinary = str_starts_with($propertyKey, 'BIN;');
        $propertyKey = strtolower($propertyKey);
        $value = $defaultValue;

        if ($isBinary) {
            $propertyKey = substr($propertyKey, strlen('BIN;'));
        }

        if (isset($userDetails[$propertyKey])) {
            $value = (is_array($userDetails[$propertyKey]) ? $userDetails[$propertyKey][0] : $userDetails[$propertyKey]);
            if ($isBinary) {
                $value = bin2hex($value);
            }
        }

        return $value;
    }

    /**
     * Check if the given credentials are valid for the given user.
     *
     * @throws LdapException
     */
    public function validateUserCredentials(?array $ldapUserDetails, string $password): bool
    {
        if (is_null($ldapUserDetails)) {
            return false;
        }

        $ldapConnection = $this->getConnection();

        try {
            $ldapBind = $this->ldap->bind($ldapConnection, $ldapUserDetails['dn'], $password);
        } catch (ErrorException $e) {
            $ldapBind = false;
        }

        return $ldapBind;
    }

    /**
     * Bind the system user to the LDAP connection using the given credentials
     * otherwise anonymous access is attempted.
     *
     * @param resource|\LDAP\Connection $connection
     *
     * @throws LdapException
     */
    protected function bindSystemUser($connection): void
    {
        $ldapDn = $this->config['dn'];
        $ldapPass = $this->config['pass'];

        $isAnonymous = ($ldapDn === false || $ldapPass === false);
        if ($isAnonymous) {
            $ldapBind = $this->ldap->bind($connection);
        } else {
            $ldapBind = $this->ldap->bind($connection, $ldapDn, $ldapPass);
        }

        if (!$ldapBind) {
            throw new LdapException(($isAnonymous ? trans('errors.ldap_fail_anonymous') : trans('errors.ldap_fail_authed')));
        }
    }

    /**
     * Get the connection to the LDAP server.
     * Creates a new connection if one does not exist.
     *
     * @throws LdapException
     *
     * @return resource|\LDAP\Connection
     */
    protected function getConnection()
    {
        if ($this->ldapConnection !== null) {
            return $this->ldapConnection;
        }

        // Check LDAP extension in installed
        if (!function_exists('ldap_connect') && config('app.env') !== 'testing') {
            throw new LdapException(trans('errors.ldap_extension_not_installed'));
        }

        // Disable certificate verification.
        // This option works globally and must be set before a connection is created.
        if ($this->config['tls_insecure']) {
            $this->ldap->setOption(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
        }

        // Configure any user-provided CA cert files for LDAP.
        // This option works globally and must be set before a connection is created.
        if ($this->config['tls_ca_cert']) {
            $this->configureTlsCaCerts($this->config['tls_ca_cert']);
        }

        $ldapHost = $this->parseServerString($this->config['server']);
        $ldapConnection = $this->ldap->connect($ldapHost);

        if ($ldapConnection === false) {
            throw new LdapException(trans('errors.ldap_cannot_connect'));
        }

        // Set any required options
        if ($this->config['version']) {
            $this->ldap->setVersion($ldapConnection, $this->config['version']);
        }

        // Start and verify TLS if it's enabled
        if ($this->config['start_tls']) {
            try {
                $started = $this->ldap->startTls($ldapConnection);
            } catch (\Exception $exception) {
                $error = $exception->getMessage() . ' :: ' . ldap_error($ldapConnection);
                ldap_get_option($ldapConnection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $detail);
                Log::info("LDAP STARTTLS failure: {$error} {$detail}");
                throw new LdapException('Could not start TLS connection. Further details in the application log.');
            }
            if (!$started) {
                throw new LdapException('Could not start TLS connection');
            }
        }

        $this->ldapConnection = $ldapConnection;

        return $this->ldapConnection;
    }

    /**
     * Configure TLS CA certs globally for ldap use.
     * This will detect if the given path is a directory or file, and set the relevant
     * LDAP TLS options appropriately otherwise throw an exception if no file/folder found.
     *
     * Note: When using a folder, certificates are expected to be correctly named by hash
     * which can be done via the c_rehash utility.
     *
     * @throws LdapException
     */
    protected function configureTlsCaCerts(string $caCertPath): void
    {
        $errMessage = "Provided path [{$caCertPath}] for LDAP TLS CA certs could not be resolved to an existing location";
        $path = realpath($caCertPath);
        if ($path === false) {
            throw new LdapException($errMessage);
        }

        if (is_dir($path)) {
            $this->ldap->setOption(null, LDAP_OPT_X_TLS_CACERTDIR, $path);
        } else if (is_file($path)) {
            $this->ldap->setOption(null, LDAP_OPT_X_TLS_CACERTFILE, $path);
        } else {
            throw new LdapException($errMessage);
        }
    }

    /**
     * Parse an LDAP server string and return the host suitable for a connection.
     * Is flexible to formats such as 'ldap.example.com:8069' or 'ldaps://ldap.example.com'.
     */
    protected function parseServerString(string $serverString): string
    {
        if (str_starts_with($serverString, 'ldaps://') || str_starts_with($serverString, 'ldap://')) {
            return $serverString;
        }

        return "ldap://{$serverString}";
    }

    /**
     * Build a filter string by injecting common variables.
     * Both "${var}" and "{var}" style placeholders are supported.
     * Dollar based are old format but supported for compatibility.
     */
    protected function buildFilter(string $filterString, array $attrs): string
    {
        $newAttrs = [];
        foreach ($attrs as $key => $attrText) {
            $escapedText = $this->ldap->escape($attrText);
            $oldVarKey = '${' . $key . '}';
            $newVarKey = '{' . $key . '}';
            $newAttrs[$oldVarKey] = $escapedText;
            $newAttrs[$newVarKey] = $escapedText;
        }

        return strtr($filterString, $newAttrs);
    }

    /**
     * Get the groups a user is a part of on ldap.
     *
     * @throws LdapException
     * @throws JsonDebugException
     */
    public function getUserGroups(string $userName): array
    {
        $groupsAttr = $this->config['group_attribute'];
        $user = $this->getUserWithAttributes($userName, [$groupsAttr]);

        if ($user === null) {
            return [];
        }

        $userGroups = $this->extractGroupsFromSearchResponseEntry($user);
        $allGroups = $this->getGroupsRecursive($userGroups, []);
        $formattedGroups = $this->extractGroupNamesFromLdapGroupDns($allGroups);

        if ($this->config['dump_user_groups']) {
            throw new JsonDebugException([
                'details_from_ldap'            => $user,
                'parsed_direct_user_groups'    => $userGroups,
                'parsed_recursive_user_groups' => $allGroups,
                'parsed_resulting_group_names' => $formattedGroups,
            ]);
        }

        return $formattedGroups;
    }

    protected function extractGroupNamesFromLdapGroupDns(array $groupDNs): array
    {
        $names = [];

        foreach ($groupDNs as $groupDN) {
            $exploded = $this->ldap->explodeDn($groupDN, 1);
            if ($exploded !== false && count($exploded) > 0) {
                $names[] = $exploded[0];
            }
        }

        return array_unique($names);
    }

    /**
     * Build an array of all relevant groups DNs after recursively scanning
     * across parents of the groups given.
     *
     * @throws LdapException
     */
    protected function getGroupsRecursive(array $groupDNs, array $checked): array
    {
        $groupsToAdd = [];
        foreach ($groupDNs as $groupDN) {
            if (in_array($groupDN, $checked)) {
                continue;
            }

            $parentGroups = $this->getParentsOfGroup($groupDN);
            $groupsToAdd = array_merge($groupsToAdd, $parentGroups);
            $checked[] = $groupDN;
        }

        $uniqueDNs = array_unique(array_merge($groupDNs, $groupsToAdd), SORT_REGULAR);

        if (empty($groupsToAdd)) {
            return $uniqueDNs;
        }

        return $this->getGroupsRecursive($uniqueDNs, $checked);
    }

    /**
     * @throws LdapException
     */
    protected function getParentsOfGroup(string $groupDN): array
    {
        $groupsAttr = strtolower($this->config['group_attribute']);
        $ldapConnection = $this->getConnection();
        $this->bindSystemUser($ldapConnection);

        $followReferrals = $this->config['follow_referrals'] ? 1 : 0;
        $this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
        $read = $this->ldap->read($ldapConnection, $groupDN, '(objectClass=*)', [$groupsAttr]);
        $results = $this->ldap->getEntries($ldapConnection, $read);
        if ($results['count'] === 0) {
            return [];
        }

        return $this->extractGroupsFromSearchResponseEntry($results[0]);
    }

    /**
     * Extract an array of group DN values from the given LDAP search response entry
     */
    protected function extractGroupsFromSearchResponseEntry(array $ldapEntry): array
    {
        $groupsAttr = strtolower($this->config['group_attribute']);
        $groupDNs = [];
        $count = 0;

        if (isset($ldapEntry[$groupsAttr]['count'])) {
            $count = (int) $ldapEntry[$groupsAttr]['count'];
        }

        for ($i = 0; $i < $count; $i++) {
            $dn = $ldapEntry[$groupsAttr][$i];
            if (!in_array($dn, $groupDNs)) {
                $groupDNs[] = $dn;
            }
        }

        return $groupDNs;
    }

    /**
     * Sync the LDAP groups to the user roles for the current user.
     *
     * @throws LdapException
     * @throws JsonDebugException
     */
    public function syncGroups(User $user, string $username): void
    {
        $userLdapGroups = $this->getUserGroups($username);
        $this->groupSyncService->syncUserWithFoundGroups($user, $userLdapGroups, $this->config['remove_from_groups']);
    }

    /**
     * Save and attach an avatar image, if found in the ldap details, and attach
     * to the given user model.
     */
    public function saveAndAttachAvatar(User $user, array $ldapUserDetails): void
    {
        if (is_null(config('services.ldap.thumbnail_attribute')) || is_null($ldapUserDetails['avatar'])) {
            return;
        }

        try {
            $imageData = $ldapUserDetails['avatar'];
            $this->userAvatars->assignToUserFromExistingData($user, $imageData, 'jpg');
        } catch (\Exception $exception) {
            Log::info("Failed to use avatar image from LDAP data for user id {$user->id}");
        }
    }
}


================================================
FILE: app/Access/LoginService.php
================================================
<?php

namespace BookStack\Access;

use BookStack\Access\Mfa\MfaSession;
use BookStack\Activity\ActivityType;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Exceptions\LoginAttemptInvalidUserException;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Facades\Activity;
use BookStack\Facades\Theme;
use BookStack\Permissions\Permission;
use BookStack\Theming\ThemeEvents;
use BookStack\Users\Models\User;
use Exception;

class LoginService
{
    protected const LAST_LOGIN_ATTEMPTED_SESSION_KEY = 'auth-login-last-attempted';

    public function __construct(
        protected MfaSession $mfaSession,
        protected EmailConfirmationService $emailConfirmationService,
        protected SocialDriverManager $socialDriverManager,
    ) {
    }

    /**
     * Log the given user into the system.
     * Will start a login of the given user but will prevent if there's
     * a reason to (MFA or Unconfirmed Email).
     * Returns a boolean to indicate the current login result.
     *
     * @throws StoppedAuthenticationException|LoginAttemptInvalidUserException
     */
    public function login(User $user, string $method, bool $remember = false): void
    {
        if ($user->isGuest()) {
            throw new LoginAttemptInvalidUserException('Login not allowed for guest user');
        }

        if ($this->awaitingEmailConfirmation($user) || $this->needsMfaVerification($user)) {
            $this->setLastLoginAttemptedForUser($user, $method, $remember);

            throw new StoppedAuthenticationException($user, $this);
        }

        $this->clearLastLoginAttempted();
        auth()->login($user, $remember);
        Activity::add(ActivityType::AUTH_LOGIN, "{$method}; {$user->logDescriptor()}");
        Theme::dispatch(ThemeEvents::AUTH_LOGIN, $method, $user);

        // Authenticate on all session guards if a likely admin
        if ($user->can(Permission::UsersManage) && $user->can(Permission::UserRolesManage)) {
            $guards = ['standard', 'ldap', 'saml2', 'oidc'];
            foreach ($guards as $guard) {
                auth($guard)->login($user);
            }
        }
    }

    /**
     * Reattempt a system login after a previous stopped attempt.
     *
     * @throws Exception
     */
    public function reattemptLoginFor(User $user): void
    {
        if ($user->id !== ($this->getLastLoginAttemptUser()->id ?? null)) {
            throw new Exception('Login reattempt user does align with current session state');
        }

        $lastLoginDetails = $this->getLastLoginAttemptDetails();
        $this->login($user, $lastLoginDetails['method'], $lastLoginDetails['remember'] ?? false);
    }

    /**
     * Get the last user that was attempted to be logged in.
     * Only exists if the last login attempt had correct credentials
     * but had been prevented by a secondary factor.
     */
    public function getLastLoginAttemptUser(): ?User
    {
        $id = $this->getLastLoginAttemptDetails()['user_id'];

        return User::query()->where('id', '=', $id)->first();
    }

    /**
     * Get the details of the last login attempt.
     * Checks upon a ttl of about 1 hour since that last attempted login.
     *
     * @return array{user_id: ?string, method: ?string, remember: bool}
     */
    protected function getLastLoginAttemptDetails(): array
    {
        $value = session()->get(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY);
        if (!$value) {
            return ['user_id' => null, 'method' => null, 'remember' => false];
        }

        [$id, $method, $remember, $time] = explode(':', $value);
        $hourAgo = time() - (60 * 60);
        if ($time < $hourAgo) {
            $this->clearLastLoginAttempted();

            return ['user_id' => null, 'method' => null, 'remember' => false];
        }

        return ['user_id' => $id, 'method' => $method, 'remember' => boolval($remember)];
    }

    /**
     * Set the last login-attempted user.
     * Must be only used when credentials are correct and a login could be
     * achieved, but a secondary factor has stopped the login.
     */
    protected function setLastLoginAttemptedForUser(User $user, string $method, bool $remember): void
    {
        session()->put(
            self::LAST_LOGIN_ATTEMPTED_SESSION_KEY,
            implode(':', [$user->id, $method, $remember, time()])
        );
    }

    /**
     * Clear the last login attempted session value.
     */
    protected function clearLastLoginAttempted(): void
    {
        session()->remove(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY);
    }

    /**
     * Check if MFA verification is needed.
     */
    public function needsMfaVerification(User $user): bool
    {
        return !$this->mfaSession->isVerifiedForUser($user) && $this->mfaSession->isRequiredForUser($user);
    }

    /**
     * Check if the given user is awaiting email confirmation.
     */
    public function awaitingEmailConfirmation(User $user): bool
    {
        return $this->emailConfirmationService->confirmationRequired() && !$user->email_confirmed;
    }

    /**
     * Attempt the login of a user using the given credentials.
     * Meant to mirror Laravel's default guard 'attempt' method
     * but in a manner that always routes through our login system.
     * May interrupt the flow if extra authentication requirements are imposed.
     *
     * @throws StoppedAuthenticationException
     * @throws LoginAttemptException
     */
    public function attempt(array $credentials, string $method, bool $remember = false): bool
    {
        if ($this->areCredentialsForGuest($credentials)) {
            return false;
        }

        $result = auth()->attempt($credentials, $remember);
        if ($result) {
            $user = auth()->user();
            auth()->logout();
            try {
                $this->login($user, $method, $remember);
            } catch (LoginAttemptInvalidUserException $e) {
                // Catch and return false for non-login accounts
                // so it looks like a normal invalid login.
                return false;
            }
        }

        return $result;
    }

    /**
     * Check if the given credentials are likely for the system guest account.
     */
    protected function areCredentialsForGuest(array $credentials): bool
    {
        if (isset($credentials['email'])) {
            return User::query()->where('email', '=', $credentials['email'])
                ->where('system_name', '=', 'public')
                ->exists();
        }

        return false;
    }

    /**
     * Logs the current user out of the application.
     * Returns an app post-redirect path.
     */
    public function logout(): string
    {
        auth()->logout();
        session()->invalidate();
        session()->regenerateToken();

        return $this->shouldAutoInitiate() ? '/login?prevent_auto_init=true' : '/';
    }

    /**
     * Check if login auto-initiate should be active based upon authentication config.
     */
    public function shouldAutoInitiate(): bool
    {
        $autoRedirect = config('auth.auto_initiate');
        if (!$autoRedirect) {
            return false;
        }

        $socialDrivers = $this->socialDriverManager->getActive();
        $authMethod = config('auth.method');

        return count($socialDrivers) === 0 && in_array($authMethod, ['oidc', 'saml2']);
    }
}


================================================
FILE: app/Access/Mfa/BackupCodeService.php
================================================
<?php

namespace BookStack\Access\Mfa;

use Illuminate\Support\Str;

class BackupCodeService
{
    /**
     * Generate a new set of 16 backup codes.
     */
    public function generateNewSet(): array
    {
        $codes = [];
        while (count($codes) < 16) {
            $code = Str::random(5) . '-' . Str::random(5);
            if (!in_array($code, $codes)) {
                $codes[] = strtolower($code);
            }
        }

        return $codes;
    }

    /**
     * Check if the given code matches one of the available options.
     */
    public function inputCodeExistsInSet(string $code, string $codeSet): bool
    {
        $cleanCode = $this->cleanInputCode($code);
        $codes = json_decode($codeSet);

        return in_array($cleanCode, $codes);
    }

    /**
     * Remove the given input code from the given available options.
     * Will return a JSON string containing the codes.
     */
    public function removeInputCodeFromSet(string $code, string $codeSet): string
    {
        $cleanCode = $this->cleanInputCode($code);
        $codes = json_decode($codeSet);
        $pos = array_search($cleanCode, $codes, true);
        array_splice($codes, $pos, 1);

        return json_encode($codes);
    }

    /**
     * Count the number of codes in the given set.
     */
    public function countCodesInSet(string $codeSet): int
    {
        return count(json_decode($codeSet));
    }

    protected function cleanInputCode(string $code): string
    {
        return strtolower(str_replace(' ', '-', trim($code)));
    }
}


================================================
FILE: app/Access/Mfa/MfaSession.php
================================================
<?php

namespace BookStack\Access\Mfa;

use BookStack\Users\Models\User;

class MfaSession
{
    /**
     * Check if MFA is required for the given user.
     */
    public function isRequiredForUser(User $user): bool
    {
        return $user->mfaValues()->exists() || $this->userRoleEnforcesMfa($user);
    }

    /**
     * Check if the given user is pending MFA setup.
     * (MFA required but not yet configured).
     */
    public function isPendingMfaSetup(User $user): bool
    {
        return $this->isRequiredForUser($user) && !$user->mfaValues()->exists();
    }

    /**
     * Check if a role of the given user enforces MFA.
     */
    protected function userRoleEnforcesMfa(User $user): bool
    {
        return $user->roles()
            ->where('mfa_enforced', '=', true)
            ->exists();
    }

    /**
     * Check if the current MFA session has already been verified for the given user.
     */
    public function isVerifiedForUser(User $user): bool
    {
        return session()->get($this->getMfaVerifiedSessionKey($user)) === 'true';
    }

    /**
     * Mark the current session as MFA-verified.
     */
    public function markVerifiedForUser(User $user): void
    {
        session()->put($this->getMfaVerifiedSessionKey($user), 'true');
    }

    /**
     * Get the session key in which the MFA verification status is stored.
     */
    protected function getMfaVerifiedSessionKey(User $user): string
    {
        return 'mfa-verification-passed:' . $user->id;
    }
}


================================================
FILE: app/Access/Mfa/MfaValue.php
================================================
<?php

namespace BookStack\Access\Mfa;

use BookStack\Users\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

/**
 * @property int    $id
 * @property int    $user_id
 * @property string $method
 * @property string $value
 * @property Carbon $created_at
 * @property Carbon $updated_at
 */
class MfaValue extends Model
{
    use HasFactory;

    protected static $unguarded = true;

    const METHOD_TOTP = 'totp';
    const METHOD_BACKUP_CODES = 'backup_codes';

    /**
     * Get all the MFA methods available.
     */
    public static function allMethods(): array
    {
        return [self::METHOD_TOTP, self::METHOD_BACKUP_CODES];
    }

    /**
     * Upsert a new MFA value for the given user and method
     * using the provided value.
     */
    public static function upsertWithValue(User $user, string $method, string $value): void
    {
        /** @var MfaValue $mfaVal */
        $mfaVal = static::query()->firstOrNew([
            'user_id' => $user->id,
            'method'  => $method,
        ]);
        $mfaVal->setValue($value);
        $mfaVal->save();
    }

    /**
     * Easily get the decrypted MFA value for the given user and method.
     */
    public static function getValueForUser(User $user, string $method): ?string
    {
        /** @var MfaValue $mfaVal */
        $mfaVal = static::query()
            ->where('user_id', '=', $user->id)
            ->where('method', '=', $method)
            ->first();

        return $mfaVal ? $mfaVal->getValue() : null;
    }

    /**
     * Decrypt the value attribute upon access.
     */
    protected function getValue(): string
    {
        return decrypt($this->value);
    }

    /**
     * Encrypt the value attribute upon access.
     */
    protected function setValue($value): void
    {
        $this->value = encrypt($value);
    }
}


================================================
FILE: app/Access/Mfa/TotpService.php
================================================
<?php

namespace BookStack\Access\Mfa;

use BaconQrCode\Renderer\Color\Rgb;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\RendererStyle\Fill;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;
use BookStack\Users\Models\User;
use PragmaRX\Google2FA\Google2FA;
use PragmaRX\Google2FA\Support\Constants;

class TotpService
{
    public function __construct(
        protected Google2FA $google2fa
    ) {
        $this->google2fa = $google2fa;
        // Use SHA1 as a default, Personal testing of other options in 2021 found
        // many apps lack support for other algorithms yet still will scan
        // the code causing a confusing UX.
        $this->google2fa->setAlgorithm(Constants::SHA1);
    }

    /**
     * Generate a new totp secret key.
     */
    public function generateSecret(): string
    {
        /** @noinspection PhpUnhandledExceptionInspection */
        return $this->google2fa->generateSecretKey();
    }

    /**
     * Generate a TOTP URL from a secret key.
     */
    public function generateUrl(string $secret, User $user): string
    {
        return $this->google2fa->getQRCodeUrl(
            setting('app-name'),
            $user->email,
            $secret
        );
    }

    /**
     * Generate a QR code to display a TOTP URL.
     */
    public function generateQrCodeSvg(string $url): string
    {
        $color = Fill::uniformColor(new Rgb(255, 255, 255), new Rgb(32, 110, 167));

        return (new Writer(
            new ImageRenderer(
                new RendererStyle(192, 4, null, null, $color),
                new SvgImageBackEnd()
            )
        ))->writeString($url);
    }

    /**
     * Verify that the user provided code is valid for the secret.
     * The secret must be known, not user-provided.
     */
    public function verifyCode(string $code, string $secret): bool
    {
        /** @noinspection PhpUnhandledExceptionInspection */
        return $this->google2fa->verifyKey($secret, $code);
    }
}


================================================
FILE: app/Access/Mfa/TotpValidationRule.php
================================================
<?php

namespace BookStack\Access\Mfa;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class TotpValidationRule implements ValidationRule
{
    /**
     * Create a new rule instance.
     * Takes the TOTP secret that must be system provided, not user provided.
     */
    public function __construct(
        protected string $secret,
        protected TotpService $totpService,
    ) {
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $passes = $this->totpService->verifyCode($value, $this->secret);
        if (!$passes) {
            $fail(trans('validation.totp'));
        }
    }
}


================================================
FILE: app/Access/Notifications/ConfirmEmailNotification.php
================================================
<?php

namespace BookStack\Access\Notifications;

use BookStack\App\MailNotification;
use BookStack\Users\Models\User;
use Illuminate\Notifications\Messages\MailMessage;

class ConfirmEmailNotification extends MailNotification
{
    public function __construct(
        public string $token
    ) {
    }

    public function toMail(User $notifiable): MailMessage
    {
        $appName = ['appName' => setting('app-name')];

        return $this->newMailMessage()
                ->subject(trans('auth.email_confirm_subject', $appName))
                ->greeting(trans('auth.email_confirm_greeting', $appName))
                ->line(trans('auth.email_confirm_text'))
                ->action(trans('auth.email_confirm_action'), url('/register/confirm/' . $this->token));
    }
}


================================================
FILE: app/Access/Notifications/ResetPasswordNotification.php
================================================
<?php

namespace BookStack\Access\Notifications;

use BookStack\App\MailNotification;
use BookStack\Users\Models\User;
use Illuminate\Notifications\Messages\MailMessage;

class ResetPasswordNotification extends MailNotification
{
    public function __construct(
        public string $token
    ) {
    }

    public function toMail(User $notifiable): MailMessage
    {
        return $this->newMailMessage()
            ->subject(trans('auth.email_reset_subject', ['appName' => setting('app-name')]))
            ->line(trans('auth.email_reset_text'))
            ->action(trans('auth.reset_password'), url('password/reset/' . $this->token))
            ->line(trans('auth.email_reset_not_requested'));
    }
}


================================================
FILE: app/Access/Notifications/UserInviteNotification.php
================================================
<?php

namespace BookStack\Access\Notifications;

use BookStack\App\MailNotification;
use BookStack\Users\Models\User;
use Illuminate\Notifications\Messages\MailMessage;

class UserInviteNotification extends MailNotification
{
    public function __construct(
        public string $token
    ) {
    }

    public function toMail(User $notifiable): MailMessage
    {
        $appName = ['appName' => setting('app-name')];
        $locale = $notifiable->getLocale();

        return $this->newMailMessage($locale)
                ->subject($locale->trans('auth.user_invite_email_subject', $appName))
                ->greeting($locale->trans('auth.user_invite_email_greeting', $appName))
                ->line($locale->trans('auth.user_invite_email_text'))
                ->action($locale->trans('auth.user_invite_email_action'), url('/register/invite/' . $this->token));
    }
}


================================================
FILE: app/Access/Oidc/OidcAccessToken.php
================================================
<?php

namespace BookStack\Access\Oidc;

use InvalidArgumentException;
use League\OAuth2\Client\Token\AccessToken;

class OidcAccessToken extends AccessToken
{
    /**
     * Constructs an access token.
     *
     * @param array $options An array of options returned by the service provider
     *                       in the access token request. The `access_token` option is required.
     *
     * @throws InvalidArgumentException if `access_token` is not provided in `$options`.
     */
    public function __construct(array $options = [])
    {
        parent::__construct($options);
        $this->validate($options);
    }

    /**
     * Validate this access token response for OIDC.
     * As per https://openid.net/specs/openid-connect-basic-1_0.html#TokenOK.
     */
    private function validate(array $options): void
    {
        // access_token: REQUIRED. Access Token for the UserInfo Endpoint.
        // Performed on the extended class

        // token_type: REQUIRED. OAuth 2.0 Token Type value. The value MUST be Bearer, as specified in OAuth 2.0
        // Bearer Token Usage [RFC6750], for Clients using this subset.
        // Note that the token_type value is case-insensitive.
        if (strtolower(($options['token_type'] ?? '')) !== 'bearer') {
            throw new InvalidArgumentException('The response token type MUST be "Bearer"');
        }

        // id_token: REQUIRED. ID Token.
        if (empty($options['id_token'])) {
            throw new InvalidArgumentException('An "id_token" property must be provided');
        }
    }

    /**
     * Get the id token value from this access token response.
     */
    public function getIdToken(): string
    {
        return $this->getValues()['id_token'];
    }
}


================================================
FILE: app/Access/Oidc/OidcException.php
================================================
<?php

namespace BookStack\Access\Oidc;

use Exception;

class OidcException extends Exception
{
}


================================================
FILE: app/Access/Oidc/OidcIdToken.php
================================================
<?php

namespace BookStack\Access\Oidc;

class OidcIdToken extends OidcJwtWithClaims implements ProvidesClaims
{
    /**
     * Validate all possible parts of the id token.
     *
     * @throws OidcInvalidTokenException
     */
    public function validate(string $clientId): bool
    {
        parent::validateCommonTokenDetails($clientId);
        $this->validateTokenClaims($clientId);

        return true;
    }

    /**
     * Validate the claims of the token.
     * As per https://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation.
     *
     * @throws OidcInvalidTokenException
     */
    protected function validateTokenClaims(string $clientId): void
    {
        // 1. The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
        // MUST exactly match the value of the iss (issuer) Claim.
        // Already done in parent.

        // 2. The Client MUST validate that the aud (audience) Claim contains its client_id value registered
        // at the Issuer identified by the iss (issuer) Claim as an audience. The ID Token MUST be rejected
        // if the ID Token does not list the Client as a valid audience, or if it contains additional
        // audiences not trusted by the Client.
        // Partially done in parent.
        $aud = is_string($this->payload['aud']) ? [$this->payload['aud']] : $this->payload['aud'];
        if (count($aud) !== 1) {
            throw new OidcInvalidTokenException('Token audience value has ' . count($aud) . ' values, Expected 1');
        }

        // 3. If the ID Token contains multiple audiences, the Client SHOULD verify that an azp Claim is present.
        // NOTE: Addressed by enforcing a count of 1 above.

        // 4. If an azp (authorized party) Claim is present, the Client SHOULD verify that its client_id
        // is the Claim Value.
        if (isset($this->payload['azp']) && $this->payload['azp'] !== $clientId) {
            throw new OidcInvalidTokenException('Token authorized party exists but does not match the expected client_id');
        }

        // 5. The current time MUST be before the time represented by the exp Claim
        // (possibly allowing for some small leeway to account for clock skew).
        if (empty($this->payload['exp'])) {
            throw new OidcInvalidTokenException('Missing token expiration time value');
        }

        $skewSeconds = 120;
        $now = time();
        if ($now >= (intval($this->payload['exp']) + $skewSeconds)) {
            throw new OidcInvalidTokenException('Token has expired');
        }

        // 6. The iat Claim can be used to reject tokens that were issued too far away from the current time,
        // limiting the amount of time that nonces need to be stored to prevent attacks.
        // The acceptable range is Client specific.
        if (empty($this->payload['iat'])) {
            throw new OidcInvalidTokenException('Missing token issued at time value');
        }

        $dayAgo = time() - 86400;
        $iat = intval($this->payload['iat']);
        if ($iat > ($now + $skewSeconds) || $iat < $dayAgo) {
            throw new OidcInvalidTokenException('Token issue at time is not recent or is invalid');
        }

        // 7. If the acr Claim was requested, the Client SHOULD check that the asserted Claim Value is appropriate.
        // The meaning and processing of acr Claim Values is out of scope for this document.
        // NOTE: Not used for our case here. acr is not requested.

        // 8. When a max_age request is made, the Client SHOULD check the auth_time Claim value and request
        // re-authentication if it determines too much time has elapsed since the last End-User authentication.
        // NOTE: Not used for our case here. A max_age request is not made.

        // Custom: Ensure the "sub" (Subject) Claim exists and has a value.
        if (empty($this->payload['sub'])) {
            throw new OidcInvalidTokenException('Missing token subject value');
        }
    }
}


================================================
FILE: app/Access/Oidc/OidcInvalidKeyException.php
================================================
<?php

namespace BookStack\Access\Oidc;

class OidcInvalidKeyException extends \Exception
{
}


================================================
FILE: app/Access/Oidc/OidcInvalidTokenException.php
================================================
<?php

namespace BookStack\Access\Oidc;

use Exception;

class OidcInvalidTokenException extends Exception
{
}


================================================
FILE: app/Access/Oidc/OidcIssuerDiscoveryException.php
================================================
<?php

namespace BookStack\Access\Oidc;

use Exception;

class OidcIssuerDiscoveryException extends Exception
{
}


================================================
FILE: app/Access/Oidc/OidcJwtSigningKey.php
================================================
<?php

namespace BookStack\Access\Oidc;

use phpseclib3\Crypt\Common\PublicKey;
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Crypt\RSA;
use phpseclib3\Math\BigInteger;

class OidcJwtSigningKey
{
    /**
     * @var PublicKey
     */
    protected $key;

    /**
     * Can be created either from a JWK parameter array or local file path to load a certificate from.
     * Examples:
     * 'file:///var/www/cert.pem'
     * ['kty' => 'RSA', 'alg' => 'RS256', 'n' => 'abc123...'].
     *
     * @param array|string $jwkOrKeyPath
     *
     * @throws OidcInvalidKeyException
     */
    public function __construct($jwkOrKeyPath)
    {
        if (is_array($jwkOrKeyPath)) {
            $this->loadFromJwkArray($jwkOrKeyPath);
        } elseif (is_string($jwkOrKeyPath) && strpos($jwkOrKeyPath, 'file://') === 0) {
            $this->loadFromPath($jwkOrKeyPath);
        } else {
            throw new OidcInvalidKeyException('Unexpected type of key value provided');
        }
    }

    /**
     * @throws OidcInvalidKeyException
     */
    protected function loadFromPath(string $path)
    {
        try {
            $key = PublicKeyLoader::load(
                file_get_contents($path)
            );
        } catch (\Exception $exception) {
            throw new OidcInvalidKeyException("Failed to load key from file path with error: {$exception->getMessage()}");
        }

        if (!$key instanceof RSA) {
            throw new OidcInvalidKeyException('Key loaded from file path is not an RSA key as expected');
        }

        $this->key = $key->withPadding(RSA::SIGNATURE_PKCS1);
    }

    /**
     * @throws OidcInvalidKeyException
     */
    protected function loadFromJwkArray(array $jwk)
    {
        // 'alg' is optional for a JWK, but we will still attempt to validate if
        // it exists otherwise presume it will be compatible.
        $alg = $jwk['alg'] ?? null;
        if ($jwk['kty'] !== 'RSA' || !(is_null($alg) || $alg === 'RS256')) {
            throw new OidcInvalidKeyException("Only RS256 keys are currently supported. Found key using {$alg}");
        }

        // 'use' is optional for a JWK but we assume 'sig' where no value exists since that's what
        // the OIDC discovery spec infers since 'sig' MUST be set if encryption keys come into play.
        $use = $jwk['use'] ?? 'sig';
        if ($use !== 'sig') {
            throw new OidcInvalidKeyException("Only signature keys are currently supported. Found key for use {$jwk['use']}");
        }

        if (empty($jwk['e'])) {
            throw new OidcInvalidKeyException('An "e" parameter on the provided key is expected');
        }

        if (empty($jwk['n'])) {
            throw new OidcInvalidKeyException('A "n" parameter on the provided key is expected');
        }

        $n = strtr($jwk['n'] ?? '', '-_', '+/');

        try {
            $key = PublicKeyLoader::load([
                'e' => new BigInteger(base64_decode($jwk['e']), 256),
                'n' => new BigInteger(base64_decode($n), 256),
            ]);
        } catch (\Exception $exception) {
            throw new OidcInvalidKeyException("Failed to load key from JWK parameters with error: {$exception->getMessage()}");
        }

        if (!$key instanceof RSA) {
            throw new OidcInvalidKeyException('Key loaded from file path is not an RSA key as expected');
        }

        $this->key = $key->withPadding(RSA::SIGNATURE_PKCS1);
    }

    /**
     * Use this key to sign the given content and return the signature.
     */
    public function verify(string $content, string $signature): bool
    {
        return $this->key->verify($content, $signature);
    }

    /**
     * Convert the key to a PEM encoded key string.
     */
    public function toPem(): string
    {
        return $this->key->toString('PKCS8');
    }
}


================================================
FILE: app/Access/Oidc/OidcJwtWithClaims.php
================================================
<?php

namespace BookStack\Access\Oidc;

class OidcJwtWithClaims implements ProvidesClaims
{
    protected array $header;
    protected array $payload;
    protected string $signature;
    protected string $issuer;
    protected array $tokenParts = [];

    /**
     * @var array[]|string[]
     */
    protected array $keys;

    public function __construct(string $token, string $issuer, array $keys)
    {
        $this->keys = $keys;
        $this->issuer = $issuer;
        $this->parse($token);
    }

    /**
     * Parse the token content into its components.
     */
    protected function parse(string $token): void
    {
        $this->tokenParts = explode('.', $token);
        $this->header = $this->parseEncodedTokenPart($this->tokenParts[0]);
        $this->payload = $this->parseEncodedTokenPart($this->tokenParts[1] ?? '');
        $this->signature = $this->base64UrlDecode($this->tokenParts[2] ?? '') ?: '';
    }

    /**
     * Parse a Base64-JSON encoded token part.
     * Returns the data as a key-value array or empty array upon error.
     */
    protected function parseEncodedTokenPart(string $part): array
    {
        $json = $this->base64UrlDecode($part) ?: '{}';
        $decoded = json_decode($json, true);

        return is_array($decoded) ? $decoded : [];
    }

    /**
     * Base64URL decode. Needs some character conversions to be compatible
     * with PHP's default base64 handling.
     */
    protected function base64UrlDecode(string $encoded): string
    {
        return base64_decode(strtr($encoded, '-_', '+/'));
    }

    /**
     * Validate common parts of OIDC JWT tokens.
     *
     * @throws OidcInvalidTokenException
     */
    public function validateCommonTokenDetails(string $clientId): bool
    {
        $this->validateTokenStructure();
        $this->validateTokenSignature();
        $this->validateCommonClaims($clientId);

        return true;
    }

    /**
     * Fetch a specific claim from this token.
     * Returns null if it is null or does not exist.
     */
    public function getClaim(string $claim): mixed
    {
        return $this->payload[$claim] ?? null;
    }

    /**
     * Get all returned claims within the token.
     */
    public function getAllClaims(): array
    {
        return $this->payload;
    }

    /**
     * Replace the existing claim data of this token with that provided.
     */
    public function replaceClaims(array $claims): void
    {
        $this->payload = $claims;
    }

    /**
     * Validate the structure of the given token and ensure we have the required pieces.
     * As per https://datatracker.ietf.org/doc/html/rfc7519#section-7.2.
     *
     * @throws OidcInvalidTokenException
     */
    protected function validateTokenStructure(): void
    {
        foreach (['header', 'payload'] as $prop) {
            if (empty($this->$prop) || !is_array($this->$prop)) {
                throw new OidcInvalidTokenException("Could not parse out a valid {$prop} within the provided token");
            }
        }

        if (empty($this->signature) || !is_string($this->signature)) {
            throw new OidcInvalidTokenException('Could not parse out a valid signature within the provided token');
        }
    }

    /**
     * Validate the signature of the given token and ensure it validates against the provided key.
     *
     * @throws OidcInvalidTokenException
     */
    protected function validateTokenSignature(): void
    {
        if ($this->header['alg'] !== 'RS256') {
            throw new OidcInvalidTokenException("Only RS256 signature validation is supported. Token reports using {$this->header['alg']}");
        }

        $parsedKeys = array_map(function ($key) {
            try {
                return new OidcJwtSigningKey($key);
            } catch (OidcInvalidKeyException $e) {
                throw new OidcInvalidTokenException('Failed to read signing key with error: ' . $e->getMessage());
            }
        }, $this->keys);

        $parsedKeys = array_filter($parsedKeys);

        $contentToSign = $this->tokenParts[0] . '.' . $this->tokenParts[1];
        /** @var OidcJwtSigningKey $parsedKey */
        foreach ($parsedKeys as $parsedKey) {
            if ($parsedKey->verify($contentToSign, $this->signature)) {
                return;
            }
        }

        throw new OidcInvalidTokenException('Token signature could not be validated using the provided keys');
    }

    /**
     * Validate common claims for OIDC JWT tokens.
     * As per https://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation
     * and https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
     *
     * @throws OidcInvalidTokenException
     */
    protected function validateCommonClaims(string $clientId): void
    {
        // 1. The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
        // MUST exactly match the value of the iss (issuer) Claim.
        if (empty($this->payload['iss']) || $this->issuer !== $this->payload['iss']) {
            throw new OidcInvalidTokenException('Missing or non-matching token issuer value');
        }

        // 2. The Client MUST validate that the aud (audience) Claim contains its client_id value registered
        // at the Issuer identified by the iss (issuer) Claim as an audience. The ID Token MUST be rejected
        // if the ID Token does not list the Client as a valid audience.
        if (empty($this->payload['aud'])) {
            throw new OidcInvalidTokenException('Missing token audience value');
        }

        $aud = is_string($this->payload['aud']) ? [$this->payload['aud']] : $this->payload['aud'];
        if (!in_array($clientId, $aud, true)) {
            throw new OidcInvalidTokenException('Token audience value did not match the expected client_id');
        }
    }
}


================================================
FILE: app/Access/Oidc/OidcOAuthProvider.php
================================================
<?php

namespace BookStack\Access\Oidc;

use League\OAuth2\Client\Grant\AbstractGrant;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\GenericResourceOwner;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;

/**
 * Extended OAuth2Provider for using with OIDC.
 * Credit to the https://github.com/steverhoades/oauth2-openid-connect-client
 * project for the idea of extending a League\OAuth2 client for this use-case.
 */
class OidcOAuthProvider extends AbstractProvider
{
    use BearerAuthorizationTrait;

    protected string $authorizationEndpoint;
    protected string $tokenEndpoint;

    /**
     * Scopes to use for the OIDC authorization call.
     */
    protected array $scopes = ['openid', 'profile', 'email'];

    /**
     * Returns the base URL for authorizing a client.
     */
    public function getBaseAuthorizationUrl(): string
    {
        return $this->authorizationEndpoint;
    }

    /**
     * Returns the base URL for requesting an access token.
     */
    public function getBaseAccessTokenUrl(array $params): string
    {
        return $this->tokenEndpoint;
    }

    /**
     * Returns the URL for requesting the resource owner's details.
     */
    public function getResourceOwnerDetailsUrl(AccessToken $token): string
    {
        return '';
    }

    /**
     * Add another scope to this provider upon the default.
     */
    public function addScope(string $scope): void
    {
        $this->scopes[] = $scope;
        $this->scopes = array_unique($this->scopes);
    }

    /**
     * Returns the default scopes used by this provider.
     *
     * This should only be the scopes that are required to request the details
     * of the resource owner, rather than all the available scopes.
     */
    protected function getDefaultScopes(): array
    {
        return $this->scopes;
    }

    /**
     * Returns the string that should be used to separate scopes when building
     * the URL for requesting an access token.
     */
    protected function getScopeSeparator(): string
    {
        return ' ';
    }

    /**
     * Checks a provider response for errors.
     * @throws IdentityProviderException
     */
    protected function checkResponse(ResponseInterface $response, $data): void
    {
        if ($response->getStatusCode() >= 400 || isset($data['error'])) {
            throw new IdentityProviderException(
                $data['error'] ?? $response->getReasonPhrase(),
                $response->getStatusCode(),
                (string) $response->getBody()
            );
        }
    }

    /**
     * Generates a resource owner object from a successful resource owner
     * details request.
     */
    protected function createResourceOwner(array $response, AccessToken $token): ResourceOwnerInterface
    {
        return new GenericResourceOwner($response, '');
    }

    /**
     * Creates an access token from a response.
     *
     * The grant that was used to fetch the response can be used to provide
     * additional context.
     */
    protected function createAccessToken(array $response, AbstractGrant $grant): OidcAccessToken
    {
        return new OidcAccessToken($response);
    }

    /**
     * Get the method used for PKCE code verifier hashing, which is passed
     * in the "code_challenge_method" parameter in the authorization request.
     */
    protected function getPkceMethod(): string
    {
        return static::PKCE_METHOD_S256;
    }
}


================================================
FILE: app/Access/Oidc/OidcProviderSettings.php
================================================
<?php

namespace BookStack\Access\Oidc;

use GuzzleHttp\Psr7\Request;
use Illuminate\Contracts\Cache\Repository;
use InvalidArgumentException;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;

/**
 * OpenIdConnectProviderSettings
 * Acts as a DTO for settings used within the oidc request and token handling.
 * Performs auto-discovery upon request.
 */
class OidcProviderSettings
{
    public string $issuer;
    public string $clientId;
    public string $clientSecret;
    public ?string $authorizationEndpoint;
    public ?string $tokenEndpoint;
    public ?string $endSessionEndpoint;
    public ?string $userinfoEndpoint;

    /**
     * @var string[]|array[]
     */
    public ?array $keys = [];

    public function __construct(array $settings)
    {
        $this->applySettingsFromArray($settings);
        $this->validateInitial();
    }

    /**
     * Apply an array of settings to populate setting properties within this class.
     */
    protected function applySettingsFromArray(array $settingsArray): void
    {
        foreach ($settingsArray as $key => $value) {
            if (property_exists($this, $key)) {
                $this->$key = $value;
            }
        }
    }

    /**
     * Validate any core, required properties have been set.
     *
     * @throws InvalidArgumentException
     */
    protected function validateInitial(): void
    {
        $required = ['clientId', 'clientSecret', 'issuer'];
        foreach ($required as $prop) {
            if (empty($this->$prop)) {
                throw new InvalidArgumentException("Missing required configuration \"{$prop}\" value");
            }
        }

        if (!str_starts_with($this->issuer, 'https://')) {
            throw new InvalidArgumentException('Issuer value must start with https://');
        }
    }

    /**
     * Perform a full validation on these settings.
     *
     * @throws InvalidArgumentException
     */
    public function validate(): void
    {
        $this->validateInitial();

        $required = ['keys', 'tokenEndpoint', 'authorizationEndpoint'];
        foreach ($required as $prop) {
            if (empty($this->$prop)) {
                throw new InvalidArgumentException("Missing required configuration \"{$prop}\" value");
            }
        }

        $endpointProperties = ['tokenEndpoint', 'authorizationEndpoint', 'userinfoEndpoint'];
        foreach ($endpointProperties as $prop) {
            if (is_string($this->$prop) && !str_starts_with($this->$prop, 'https://')) {
                throw new InvalidArgumentException("Endpoint value for \"{$prop}\" must start with https://");
            }
        }
    }

    /**
     * Discover and autoload settings from the configured issuer.
     *
     * @throws OidcIssuerDiscoveryException
     */
    public function discoverFromIssuer(ClientInterface $httpClient, Repository $cache, int $cacheMinutes): void
    {
        try {
            $cacheKey = 'oidc-discovery::' . $this->issuer;
            $discoveredSettings = $cache->remember($cacheKey, $cacheMinutes * 60, function () use ($httpClient) {
                return $this->loadSettingsFromIssuerDiscovery($httpClient);
            });
            $this->applySettingsFromArray($discoveredSettings);
        } catch (ClientExceptionInterface $exception) {
            throw new OidcIssuerDiscoveryException("HTTP request failed during discovery with error: {$exception->getMessage()}");
        }
    }

    /**
     * @throws OidcIssuerDiscoveryException
     * @throws ClientExceptionInterface
     */
    protected function loadSettingsFromIssuerDiscovery(ClientInterface $httpClient): array
    {
        $issuerUrl = rtrim($this->issuer, '/') . '/.well-known/openid-configuration';
        $request = new Request('GET', $issuerUrl);
        $response = $httpClient->sendRequest($request);
        $result = json_decode($response->getBody()->getContents(), true);

        if (empty($result) || !is_array($result)) {
            throw new OidcIssuerDiscoveryException("Error discovering provider settings from issuer at URL {$issuerUrl}");
        }

        if ($result['issuer'] !== $this->issuer) {
            throw new OidcIssuerDiscoveryException('Unexpected issuer value found on discovery response');
        }

        $discoveredSettings = [];

        if (!empty($result['authorization_endpoint'])) {
            $discoveredSettings['authorizationEndpoint'] = $result['authorization_endpoint'];
        }

        if (!empty($result['token_endpoint'])) {
            $discoveredSettings['tokenEndpoint'] = $result['token_endpoint'];
        }

        if (!empty($result['userinfo_endpoint'])) {
            $discoveredSettings['userinfoEndpoint'] = $result['userinfo_endpoint'];
        }

        if (!empty($result['jwks_uri'])) {
            $keys = $this->loadKeysFromUri($result['jwks_uri'], $httpClient);
            $discoveredSettings['keys'] = $this->filterKeys($keys);
        }

        if (!empty($result['end_session_endpoint'])) {
            $discoveredSettings['endSessionEndpoint'] = $result['end_session_endpoint'];
        }

        return $discoveredSettings;
    }

    /**
     * Filter the given JWK keys down to just those we support.
     */
    protected function filterKeys(array $keys): array
    {
        return array_filter($keys, function (array $key) {
            $alg = $key['alg'] ?? 'RS256';
            $use = $key['use'] ?? 'sig';

            return $key['kty'] === 'RSA' && $use === 'sig' && $alg === 'RS256';
        });
    }

    /**
     * Return an array of jwks as PHP key=>value arrays.
     *
     * @throws ClientExceptionInterface
     * @throws OidcIssuerDiscoveryException
     */
    protected function loadKeysFromUri(string $uri, ClientInterface $httpClient): array
    {
        $request = new Request('GET', $uri);
        $response = $httpClient->sendRequest($request);
        $result = json_decode($response->getBody()->getContents(), true);

        if (empty($result) || !is_array($result) || !isset($result['keys'])) {
            throw new OidcIssuerDiscoveryException('Error reading keys from issuer jwks_uri');
        }

        return $result['keys'];
    }

    /**
     * Get the settings needed by an OAuth provider, as a key=>value array.
     */
    public function arrayForOAuthProvider(): array
    {
        $settingKeys = ['clientId', 'clientSecret', 'authorizationEndpoint', 'tokenEndpoint', 'userinfoEndpoint'];
        $settings = [];
        foreach ($settingKeys as $setting) {
            $settings[$setting] = $this->$setting;
        }

        return $settings;
    }
}


================================================
FILE: app/Access/Oidc/OidcService.php
================================================
<?php

namespace BookStack\Access\Oidc;

use BookStack\Access\GroupSyncService;
use BookStack\Access\LoginService;
use BookStack\Access\RegistrationService;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Facades\Theme;
use BookStack\Http\HttpRequestService;
use BookStack\Theming\ThemeEvents;
use BookStack\Uploads\UserAvatars;
use BookStack\Users\Models\User;
use Illuminate\Support\Facades\Cache;
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;

/**
 * Class OpenIdConnectService
 * Handles any app-specific OIDC tasks.
 */
class OidcService
{
    public function __construct(
        protected RegistrationService $registrationService,
        protected LoginService $loginService,
        protected HttpRequestService $http,
        protected GroupSyncService $groupService,
        protected UserAvatars $userAvatars
    ) {
    }

    /**
     * Initiate an authorization flow.
     * Provides back an authorize redirect URL, in addition to other
     * details which may be required for the auth flow.
     *
     * @throws OidcException
     *
     * @return array{url: string, state: string}
     */
    public function login(): array
    {
        $settings = $this->getProviderSettings();
        $provider = $this->getProvider($settings);

        $url = $provider->getAuthorizationUrl();
        session()->put('oidc_pkce_code', $provider->getPkceCode() ?? '');

        $returnUrl = Theme::dispatch(ThemeEvents::OIDC_AUTH_PRE_REDIRECT, $url);
        if (is_string($returnUrl)) {
            $url = $returnUrl;
        }

        return [
            'url'   => $url,
            'state' => $provider->getState(),
        ];
    }

    /**
     * Process the Authorization response from the authorization server and
     * return the matching, or new if registration active, user matched to the
     * authorization server. Throws if the user cannot be auth if not authenticated.
     *
     * @throws JsonDebugException
     * @throws OidcException
     * @throws StoppedAuthenticationException
     * @throws IdentityProviderException
     */
    public function processAuthorizeResponse(?string $authorizationCode): User
    {
        $settings = $this->getProviderSettings();
        $provider = $this->getProvider($settings);

        // Set PKCE code flashed at login
        $pkceCode = session()->pull('oidc_pkce_code', '');
        $provider->setPkceCode($pkceCode);

        // Try to exchange authorization code for access token
        $accessToken = $provider->getAccessToken('authorization_code', [
            'code' => $authorizationCode,
        ]);

        return $this->processAccessTokenCallback($accessToken, $settings);
    }

    /**
     * @throws OidcException
     */
    protected function getProviderSettings(): OidcProviderSettings
    {
        $config = $this->config();
        $settings = new OidcProviderSettings([
            'issuer'                => $config['issuer'],
            'clientId'              => $config['client_id'],
            'clientSecret'          => $config['client_secret'],
            'authorizationEndpoint' => $config['authorization_endpoint'],
            'tokenEndpoint'         => $config['token_endpoint'],
            'endSessionEndpoint'    => is_string($config['end_session_endpoint']) ? $config['end_session_endpoint'] : null,
            'userinfoEndpoint'      => $config['userinfo_endpoint'],
        ]);

        // Use keys if configured
        if (!empty($config['jwt_public_key'])) {
            $settings->keys = [$config['jwt_public_key']];
        }

        // Run discovery
        if ($config['discover'] ?? false) {
            try {
                $settings->discoverFromIssuer($this->http->buildClient(5), Cache::store(null), 15);
            } catch (OidcIssuerDiscoveryException $exception) {
                throw new OidcException('OIDC Discovery Error: ' . $exception->getMessage());
            }
        }

        // Prevent use of RP-initiated logout if specifically disabled
        // Or force use of a URL if specifically set.
        if ($config['end_session_endpoint'] === false) {
            $settings->endSessionEndpoint = null;
        } else if (is_string($config['end_session_endpoint'])) {
            $settings->endSessionEndpoint = $config['end_session_endpoint'];
        }

        $settings->validate();

        return $settings;
    }

    /**
     * Load the underlying OpenID Connect Provider.
     */
    protected function getProvider(OidcProviderSettings $settings): OidcOAuthProvider
    {
        $provider = new OidcOAuthProvider([
            ...$settings->arrayForOAuthProvider(),
            'redirectUri' => url('/oidc/callback'),
        ], [
            'httpClient'     => $this->http->buildClient(5),
            'optionProvider' => new HttpBasicAuthOptionProvider(),
        ]);

        foreach ($this->getAdditionalScopes() as $scope) {
            $provider->addScope($scope);
        }

        return $provider;
    }

    /**
     * Get any user-defined addition/custom scopes to apply to the authentication request.
     *
     * @return string[]
     */
    protected function getAdditionalScopes(): array
    {
        $scopeConfig = $this->config()['additional_scopes'] ?: '';

        $scopeArr = explode(',', $scopeConfig);
        $scopeArr = array_map(fn (string $scope) => trim($scope), $scopeArr);

        return array_filter($scopeArr);
    }

    /**
     * Processes a received access token for a user. Login the user when
     * they exist, optionally registering them automatically.
     *
     * @throws OidcException
     * @throws JsonDebugException
     * @throws StoppedAuthenticationException
     */
    protected function processAccessTokenCallback(OidcAccessToken $accessToken, OidcProviderSettings $settings): User
    {
        $idTokenText = $accessToken->getIdToken();
        $idToken = new OidcIdToken(
            $idTokenText,
            $settings->issuer,
            $settings->keys,
        );

        session()->put("oidc_id_token", $idTokenText);

        $returnClaims = Theme::dispatch(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $idToken->getAllClaims(), [
            'access_token' => $accessToken->getToken(),
            'expires_in' => $accessToken->getExpires(),
            'refresh_token' => $accessToken->getRefreshToken(),
        ]);

        if (!is_null($returnClaims)) {
            $idToken->replaceClaims($returnClaims);
        }

        if ($this->config()['dump_user_details']) {
            throw new JsonDebugException($idToken->getAllClaims());
        }

        try {
            $idToken->validate($settings->clientId);
        } catch (OidcInvalidTokenException $exception) {
            throw new OidcException("ID token validation failed with error: {$exception->getMessage()}");
        }

        $userDetails = $this->getUserDetailsFromToken($idToken, $accessToken, $settings);
        if (empty($userDetails->email)) {
            throw new OidcException(trans('errors.oidc_no_email_address'));
        }
        if (empty($userDetails->name)) {
            $userDetails->name = $userDetails->externalId;
        }

        $isLoggedIn = auth()->check();
        if ($isLoggedIn) {
            throw new OidcException(trans('errors.oidc_already_logged_in'));
        }

        try {
            $user = $this->registrationService->findOrRegister(
                $userDetails->name,
                $userDetails->email,
                $userDetails->externalId
            );
        } catch (UserRegistrationException $exception) {
            throw new OidcException($exception->getMessage());
        }

        if ($this->config()['fetch_avatar'] && !$user->avatar()->exists() && $userDetails->picture) {
            $this->userAvatars->assignToUserFromUrl($user, $userDetails->picture);
        }

        if ($this->shouldSyncGroups()) {
            $detachExisting = $this->config()['remove_from_groups'];
            $this->groupService->syncUserWithFoundGroups($user, $userDetails->groups ?? [], $detachExisting);
        }

        $this->loginService->login($user, 'oidc');

        return $user;
    }

    /**
     * @throws OidcException
     */
    protected function getUserDetailsFromToken(OidcIdToken $idToken, OidcAccessToken $accessToken, OidcProviderSettings $settings): OidcUserDetails
    {
        $userDetails = new OidcUserDetails();
        $userDetails->populate(
            $idToken,
            $this->config()['external_id_claim'],
            $this->config()['display_name_claims'] ?? '',
            $this->config()['groups_claim'] ?? ''
        );

        if (!$userDetails->isFullyPopulated($this->shouldSyncGroups()) && !empty($settings->userinfoEndpoint)) {
            $provider = $this->getProvider($settings);
            $request = $provider->getAuthenticatedRequest('GET', $settings->userinfoEndpoint, $accessToken->getToken());
            $response = new OidcUserinfoResponse(
                $provider->getResponse($request),
                $settings->issuer,
                $settings->keys,
            );

            try {
                $response->validate($idToken->getClaim('sub'), $settings->clientId);
            } catch (OidcInvalidTokenException $exception) {
                throw new OidcException("Userinfo endpoint response validation failed with error: {$exception->getMessage()}");
            }

            $userDetails->populate(
                $response,
                $this->config()['external_id_claim'],
                $this->config()['display_name_claims'] ?? '',
                $this->config()['groups_claim'] ?? ''
            );
        }

        return $userDetails;
    }

    /**
     * Get the OIDC config from the application.
     */
    protected function config(): array
    {
        return config('oidc');
    }

    /**
     * Check if groups should be synced.
     */
    protected function shouldSyncGroups(): bool
    {
        return $this->config()['user_to_groups'] !== false;
    }

    /**
     * Start the RP-initiated logout flow if active, otherwise start a standard logout flow.
     * Returns a post-app-logout redirect URL.
     * Reference: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
     * @throws OidcException
     */
    public function logout(): string
    {
        $oidcToken = session()->pull("oidc_id_token");
        $defaultLogoutUrl = url($this->loginService->logout());
        $oidcSettings = $this->getProviderSettings();

        if (!$oidcSettings->endSessionEndpoint) {
            return $defaultLogoutUrl;
        }

        $endpointParams = [
            'id_token_hint' => $oidcToken,
            'post_logout_redirect_uri' => $defaultLogoutUrl,
        ];

        $joiner = str_contains($oidcSettings->endSessionEndpoint, '?') ? '&' : '?';

        return $oidcSettings->endSessionEndpoint . $joiner . http_build_query($endpointParams);
    }
}


================================================
FILE: app/Access/Oidc/OidcUserDetails.php
================================================
<?php

namespace BookStack\Access\Oidc;

use Illuminate\Support\Arr;

class OidcUserDetails
{
    public function __construct(
        public ?string $externalId = null,
        public ?string $email = null,
        public ?string $name = null,
        public ?array $groups = null,
        public ?string $picture = null,
    ) {
    }

    /**
     * Check if the user details are fully populated for our usage.
     */
    public function isFullyPopulated(bool $groupSyncActive): bool
    {
        $hasEmpty = empty($this->externalId)
            || empty($this->email)
            || empty($this->name)
            || ($groupSyncActive && $this->groups === null);

        return !$hasEmpty;
    }

    /**
     * Populate user details from the given claim data.
     */
    public function populate(
        ProvidesClaims $claims,
        string $idClaim,
        string $displayNameClaims,
        string $groupsClaim,
    ): void {
        $this->externalId = $claims->getClaim($idClaim) ?? $this->externalId;
        $this->email = $claims->getClaim('email') ?? $this->email;
        $this->name = static::getUserDisplayName($displayNameClaims, $claims) ?? $this->name;
        $this->groups = static::getUserGroups($groupsClaim, $claims) ?? $this->groups;
        $this->picture = static::getPicture($claims) ?: $this->picture;
    }

    protected static function getUserDisplayName(string $displayNameClaims, ProvidesClaims $claims): string
    {
        $displayNameClaimParts = explode('|', $displayNameClaims);

        $displayName = [];
        foreach ($displayNameClaimParts as $claim) {
            $component = $claims->getClaim(trim($claim)) ?? '';
            if ($component !== '') {
                $displayName[] = $component;
            }
        }

        return implode(' ', $displayName);
    }

    protected static function getUserGroups(string $groupsClaim, ProvidesClaims $claims): ?array
    {
        if (empty($groupsClaim)) {
            return null;
        }

        $groupsList = Arr::get($claims->getAllClaims(), $groupsClaim);
        if (!is_array($groupsList)) {
            return null;
        }

        return array_values(array_filter($groupsList, function ($val) {
            return is_string($val);
        }));
    }

    protected static function getPicture(ProvidesClaims $claims): ?string
    {
        $picture = $claims->getClaim('picture');
        if (is_string($picture) && str_starts_with($picture, 'http')) {
            return $picture;
        }

        return null;
    }
}


================================================
FILE: app/Access/Oidc/OidcUserinfoResponse.php
================================================
<?php

namespace BookStack\Access\Oidc;

use Psr\Http\Message\ResponseInterface;

class OidcUserinfoResponse implements ProvidesClaims
{
    protected array $claims = [];
    protected ?OidcJwtWithClaims $jwt = null;

    public function __construct(ResponseInterface $response, string $issuer, array $keys)
    {
        $contentTypeHeaderValue = $response->getHeader('Content-Type')[0] ?? '';
        $contentType = strtolower(trim(explode(';', $contentTypeHeaderValue, 2)[0]));

        if ($contentType === 'application/json') {
            $this->claims = json_decode($response->getBody()->getContents(), true);
        }

        if ($contentType === 'application/jwt') {
            $this->jwt = new OidcJwtWithClaims($response->getBody()->getContents(), $issuer, $keys);
            $this->claims = $this->jwt->getAllClaims();
        }
    }

    /**
     * @throws OidcInvalidTokenException
     */
    public function validate(string $idTokenSub, string $clientId): bool
    {
        if (!is_null($this->jwt)) {
            $this->jwt->validateCommonTokenDetails($clientId);
        }

        $sub = $this->getClaim('sub');

        // Spec: v1.0 5.3.2: The sub (subject) Claim MUST always be returned in the UserInfo Response.
        if (!is_string($sub) || empty($sub)) {
            throw new OidcInvalidTokenException("No valid subject value found in userinfo data");
        }

        // Spec: v1.0 5.3.2: The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token;
        // if they do not match, the UserInfo Response values MUST NOT be used.
        if ($idTokenSub !== $sub) {
            throw new OidcInvalidTokenException("Subject value provided in the userinfo endpoint does not match the provided ID token value");
        }

        // Spec v1.0 5.3.4 Defines the following:
        // Verify that the OP that responded was the intended OP through a TLS server certificate check, per RFC 6125 [RFC6125].
          // This is effectively done as part of the HTTP request we're making through CURLOPT_SSL_VERIFYHOST on the request.
        // If the Client has provided a userinfo_encrypted_response_alg parameter during Registration, decrypt the UserInfo Response using the keys specified during Registration.
          // We don't currently support JWT encryption for OIDC
        // If the response was signed, the Client SHOULD validate the signature according to JWS [JWS].
          // This is done as part of the validateCommonClaims above.

        return true;
    }

    public function getClaim(string $claim): mixed
    {
        return $this->claims[$claim] ?? null;
    }

    public function getAllClaims(): array
    {
        return $this->claims;
    }
}


================================================
FILE: app/Access/Oidc/ProvidesClaims.php
================================================
<?php

namespace BookStack\Access\Oidc;

interface ProvidesClaims
{
    /**
     * Fetch a specific claim.
     * Returns null if it is null or does not exist.
     */
    public function getClaim(string $claim): mixed;

    /**
     * Get all contained claims.
     */
    public function getAllClaims(): array;
}


================================================
FILE: app/Access/RegistrationService.php
================================================
<?php

namespace BookStack\Access;

use BookStack\Activity\ActivityType;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Facades\Activity;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use BookStack\Users\Models\User;
use BookStack\Users\UserRepo;
use Exception;
use Illuminate\Support\Str;

class RegistrationService
{
    public function __construct(
        protected UserRepo $userRepo,
        protected EmailConfirmationService $emailConfirmationService,
    ) {
    }

    /**
     * Check if registrations are allowed in the app settings.
     *
     * @throws UserRegistrationException
     */
    public function ensureRegistrationAllowed()
    {
        if (!$this->registrationAllowed()) {
            throw new UserRegistrationException(trans('auth.registrations_disabled'), '/login');
        }
    }

    /**
     * Check if standard BookStack User registrations are currently allowed.
     * Does not prevent external-auth based registration.
     */
    protected function registrationAllowed(): bool
    {
        $authMethod = config('auth.method');
        $authMethodsWithRegistration = ['standard'];

        return in_array($authMethod, $authMethodsWithRegistration) && setting('registration-enabled');
    }

    /**
     * Attempt to find a user in the system otherwise register them as a new
     * user. For use with external auth systems since password is auto-generated.
     *
     * @throws UserRegistrationException
     */
    public function findOrRegister(string $name, string $email, string $externalId): User
    {
        $user = User::query()
            ->where('external_auth_id', '=', $externalId)
            ->first();

        if (is_null($user)) {
            $userData = [
                'name'             => $name,
                'email'            => $email,
                'password'         => Str::random(32),
                'external_auth_id' => $externalId,
            ];

            $user = $this->registerUser($userData, null, false);
        }

        return $user;
    }

    /**
     * The registrations flow for all users.
     *
     * @throws UserRegistrationException
     */
    public function registerUser(array $userData, ?SocialAccount $socialAccount = null, bool $emailConfirmed = false): User
    {
        $userEmail = $userData['email'];
        $authSystem = $socialAccount ? $socialAccount->driver : auth()->getDefaultDriver();

        // Email restriction
        $this->ensureEmailDomainAllowed($userEmail);

        // Ensure user does not already exist
        $alreadyUser = !is_null($this->userRepo->getByEmail($userEmail));
        if ($alreadyUser) {
            throw new UserRegistrationException(trans('errors.error_user_exists_different_creds', ['email' => $userEmail]), '/login');
        }

        /** @var ?bool $shouldRegister */
        $shouldRegister = Theme::dispatch(ThemeEvents::AUTH_PRE_REGISTER, $authSystem, $userData);
        if ($shouldRegister === false) {
            throw new UserRegistrationException(trans('errors.auth_pre_register_theme_prevention'), '/login');
        }

        // Create the user
        $newUser = $this->userRepo->createWithoutActivity($userData, $emailConfirmed);
        $newUser->attachDefaultRole();

        // Assign social account if given
        if ($socialAccount) {
            $newUser->socialAccounts()->save($socialAccount);
        }

        Activity::add(ActivityType::AUTH_REGISTER, $socialAccount ?? $newUser);
        Theme::dispatch(ThemeEvents::AUTH_REGISTER, $authSystem, $newUser);

        // Start email confirmation flow if required
        if ($this->emailConfirmationService->confirmationRequired() && !$emailConfirmed) {
            $newUser->save();

            try {
                $this->emailConfirmationService->sendConfirmation($newUser);
                session()->flash('sent-email-confirmation', true);
            } catch (Exception $e) {
                $message = trans('auth.email_confirm_send_error');

                throw new UserRegistrationException($message, '/register/confirm');
            }
        }

        return $newUser;
    }

    /**
     * Ensure that the given email meets any active email domain registration restrictions.
     * Throws if restrictions are active and the email does not match an allowed domain.
     *
     * @throws UserRegistrationException
     */
    protected function ensureEmailDomainAllowed(string $userEmail): void
    {
        $registrationRestrict = setting('registration-restrict');

        if (!$registrationRestrict) {
            return;
        }

        $restrictedEmailDomains = explode(',', str_replace(' ', '', $registrationRestrict));
        $userEmailDomain = mb_substr(mb_strrchr($userEmail, '@'), 1);
        if (!in_array($userEmailDomain, $restrictedEmailDomains)) {
            $redirect = $this->registrationAllowed() ? '/register' : '/login';

            throw new UserRegistrationException(trans('auth.registration_email_domain_invalid'), $redirect);
        }
    }
}


================================================
FILE: app/Access/Saml2Service.php
================================================
<?php

namespace BookStack\Access;

use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\SamlException;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Users\Models\User;
use Exception;
use OneLogin\Saml2\Auth;
use OneLogin\Saml2\Constants;
use OneLogin\Saml2\Error;
use OneLogin\Saml2\IdPMetadataParser;
use OneLogin\Saml2\ValidationError;

/**
 * Class Saml2Service
 * Handles any app-specific SAML tasks.
 */
class Saml2Service
{
    protected array $config;

    public function __construct(
        protected RegistrationService $registrationService,
        protected LoginService $loginService,
        protected GroupSyncService $groupSyncService
    ) {
        $this->config = config('saml2');
    }

    /**
     * Initiate a login flow.
     *
     * @throws Error
     */
    public function login(): array
    {
        $toolKit = $this->getToolkit();
        $returnRoute = url('/saml2/acs');

        return [
            'url' => $toolKit->login($returnRoute, [], false, false, true),
            'id'  => $toolKit->getLastRequestID(),
        ];
    }

    /**
     * Initiate a logout flow.
     * Returns the SAML2 request ID, and the URL to redirect the user to.
     *
     * @throws Error
     * @return array{url: string, id: ?string}
     */
    public function logout(User $user): array
    {
        $toolKit = $this->getToolkit();
        $sessionIndex = session()->get('saml2_session_index');
        $returnUrl = url($this->loginService->logout());

        try {
            $url = $toolKit->logout(
                $returnUrl,
                [],
                $user->email,
                $sessionIndex,
                true,
                Constants::NAMEID_EMAIL_ADDRESS
            );
            $id = $toolKit->getLastRequestID();
        } catch (Error $error) {
            if ($error->getCode() !== Error::SAML_SINGLE_LOGOUT_NOT_SUPPORTED) {
                throw $error;
            }

            $url = $returnUrl;
            $id = null;
        }

        return ['url' => $url, 'id' => $id];
    }

    /**
     * Process the ACS response from the idp and return the
     * matching, or new if registration active, user matched to the idp.
     * Returns null if not authenticated.
     *
     * @throws Error
     * @throws SamlException
     * @throws ValidationError
     * @throws JsonDebugException
     * @throws UserRegistrationException
     */
    public function processAcsResponse(?string $requestId, string $samlResponse): ?User
    {
        // The SAML2 toolkit expects the response to be within the $_POST superglobal
        // so we need to manually put it back there at this point.
        $_POST['SAMLResponse'] = $samlResponse;
        $toolkit = $this->getToolkit();
        $toolkit->processResponse($requestId);
        $errors = $toolkit->getErrors();

        if (!empty($errors)) {
            $reason = $toolkit->getLastErrorReason();
            $message = 'Invalid ACS Response; Errors: ' . implode(', ', $errors);
            $message .= $reason ? "; Reason: {$reason}" : '';
            throw new Error($message);
        }

        if (!$toolkit->isAuthenticated()) {
            return null;
        }

        $attrs = $toolkit->getAttributes();
        $id = $toolkit->getNameId();
        session()->put('saml2_session_index', $toolkit->getSessionIndex());

        return $this->processLoginCallback($id, $attrs);
    }

    /**
     * Process a response for the single logout service.
     *
     * @throws Error
     */
    public function processSlsResponse(?string $requestId): string
    {
        $toolkit = $this->getToolkit();

        // The $retrieveParametersFromServer in the call below will mean the library will take the query
        // parameters, used for the response signing, from the raw $_SERVER['QUERY_STRING']
        // value so that the exact encoding format is matched when checking the signature.
        // This is primarily due to ADFS encoding query params with lowercase percent encoding while
        // PHP (And most other sensible providers) standardise on uppercase.
        /** @var ?string $samlRedirect */
        $samlRedirect = $toolkit->processSLO(true, $requestId, true, null, true);
        $errors = $toolkit->getErrors();

        if (!empty($errors)) {
            throw new Error(
                'Invalid SLS Response: ' . implode(', ', $errors)
            );
        }

        $defaultBookStackRedirect = $this->loginService->logout();

        return $samlRedirect ?? $defaultBookStackRedirect;
    }

    /**
     * Get the metadata for this service provider.
     *
     * @throws Error
     */
    public function metadata(): string
    {
        $toolKit = $this->getToolkit(true);
        $settings = $toolKit->getSettings();
        $metadata = $settings->getSPMetadata();
        $errors = $settings->validateMetadata($metadata);

        if (!empty($errors)) {
            throw new Error(
                'Invalid SP metadata: ' . implode(', ', $errors),
                Error::METADATA_SP_INVALID
            );
        }

        return $metadata;
    }

    /**
     * Load the underlying Onelogin SAML2 toolkit.
     *
     * @throws Error
     * @throws Exception
     */
    protected function getToolkit(bool $spOnly = false): Auth
    {
        $settings = $this->config['onelogin'];
        $overrides = $this->config['onelogin_overrides'] ?? [];

        if ($overrides &&
Download .txt
gitextract_hlplnxq_/

├── .gitattributes
├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── api_request.yml
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── feature_request.yml
│   │   ├── language_request.yml
│   │   ├── support_request.yml
│   │   └── z_blank_request.yml
│   ├── SECURITY.md
│   ├── translators.txt
│   └── workflows/
│       ├── analyse-php.yml
│       ├── lint-js.yml
│       ├── lint-php.yml
│       ├── test-js.yml
│       ├── test-migrations.yml
│       └── test-php.yml
├── .gitignore
├── LICENSE
├── app/
│   ├── Access/
│   │   ├── Controllers/
│   │   │   ├── ConfirmEmailController.php
│   │   │   ├── ForgotPasswordController.php
│   │   │   ├── HandlesPartialLogins.php
│   │   │   ├── LoginController.php
│   │   │   ├── MfaBackupCodesController.php
│   │   │   ├── MfaController.php
│   │   │   ├── MfaTotpController.php
│   │   │   ├── OidcController.php
│   │   │   ├── RegisterController.php
│   │   │   ├── ResetPasswordController.php
│   │   │   ├── Saml2Controller.php
│   │   │   ├── SocialController.php
│   │   │   ├── ThrottlesLogins.php
│   │   │   └── UserInviteController.php
│   │   ├── EmailConfirmationService.php
│   │   ├── ExternalBaseUserProvider.php
│   │   ├── GroupSyncService.php
│   │   ├── Guards/
│   │   │   ├── AsyncExternalBaseSessionGuard.php
│   │   │   ├── ExternalBaseSessionGuard.php
│   │   │   └── LdapSessionGuard.php
│   │   ├── Ldap.php
│   │   ├── LdapService.php
│   │   ├── LoginService.php
│   │   ├── Mfa/
│   │   │   ├── BackupCodeService.php
│   │   │   ├── MfaSession.php
│   │   │   ├── MfaValue.php
│   │   │   ├── TotpService.php
│   │   │   └── TotpValidationRule.php
│   │   ├── Notifications/
│   │   │   ├── ConfirmEmailNotification.php
│   │   │   ├── ResetPasswordNotification.php
│   │   │   └── UserInviteNotification.php
│   │   ├── Oidc/
│   │   │   ├── OidcAccessToken.php
│   │   │   ├── OidcException.php
│   │   │   ├── OidcIdToken.php
│   │   │   ├── OidcInvalidKeyException.php
│   │   │   ├── OidcInvalidTokenException.php
│   │   │   ├── OidcIssuerDiscoveryException.php
│   │   │   ├── OidcJwtSigningKey.php
│   │   │   ├── OidcJwtWithClaims.php
│   │   │   ├── OidcOAuthProvider.php
│   │   │   ├── OidcProviderSettings.php
│   │   │   ├── OidcService.php
│   │   │   ├── OidcUserDetails.php
│   │   │   ├── OidcUserinfoResponse.php
│   │   │   └── ProvidesClaims.php
│   │   ├── RegistrationService.php
│   │   ├── Saml2Service.php
│   │   ├── SocialAccount.php
│   │   ├── SocialAuthService.php
│   │   ├── SocialDriverManager.php
│   │   ├── UserInviteException.php
│   │   ├── UserInviteService.php
│   │   └── UserTokenService.php
│   ├── Activity/
│   │   ├── ActivityQueries.php
│   │   ├── ActivityType.php
│   │   ├── CommentRepo.php
│   │   ├── Controllers/
│   │   │   ├── AuditLogApiController.php
│   │   │   ├── AuditLogController.php
│   │   │   ├── CommentApiController.php
│   │   │   ├── CommentController.php
│   │   │   ├── FavouriteController.php
│   │   │   ├── TagController.php
│   │   │   ├── WatchController.php
│   │   │   └── WebhookController.php
│   │   ├── DispatchWebhookJob.php
│   │   ├── Models/
│   │   │   ├── Activity.php
│   │   │   ├── Comment.php
│   │   │   ├── Favouritable.php
│   │   │   ├── Favourite.php
│   │   │   ├── Loggable.php
│   │   │   ├── MentionHistory.php
│   │   │   ├── Tag.php
│   │   │   ├── View.php
│   │   │   ├── Viewable.php
│   │   │   ├── Watch.php
│   │   │   ├── Webhook.php
│   │   │   └── WebhookTrackedEvent.php
│   │   ├── Notifications/
│   │   │   ├── Handlers/
│   │   │   │   ├── BaseNotificationHandler.php
│   │   │   │   ├── CommentCreationNotificationHandler.php
│   │   │   │   ├── CommentMentionNotificationHandler.php
│   │   │   │   ├── NotificationHandler.php
│   │   │   │   ├── PageCreationNotificationHandler.php
│   │   │   │   └── PageUpdateNotificationHandler.php
│   │   │   ├── MessageParts/
│   │   │   │   ├── EntityLinkMessageLine.php
│   │   │   │   ├── EntityPathMessageLine.php
│   │   │   │   ├── LinkedMailMessageLine.php
│   │   │   │   └── ListMessageLine.php
│   │   │   ├── Messages/
│   │   │   │   ├── BaseActivityNotification.php
│   │   │   │   ├── CommentCreationNotification.php
│   │   │   │   ├── CommentMentionNotification.php
│   │   │   │   ├── PageCreationNotification.php
│   │   │   │   └── PageUpdateNotification.php
│   │   │   └── NotificationManager.php
│   │   ├── Queries/
│   │   │   └── WebhooksAllPaginatedAndSorted.php
│   │   ├── TagRepo.php
│   │   ├── Tools/
│   │   │   ├── ActivityLogger.php
│   │   │   ├── CommentTree.php
│   │   │   ├── CommentTreeNode.php
│   │   │   ├── EntityWatchers.php
│   │   │   ├── IpFormatter.php
│   │   │   ├── MentionParser.php
│   │   │   ├── TagClassGenerator.php
│   │   │   ├── UserEntityWatchOptions.php
│   │   │   ├── WatchedParentDetails.php
│   │   │   └── WebhookFormatter.php
│   │   └── WatchLevels.php
│   ├── Api/
│   │   ├── ApiDocsController.php
│   │   ├── ApiDocsGenerator.php
│   │   ├── ApiEntityListFormatter.php
│   │   ├── ApiToken.php
│   │   ├── ApiTokenGuard.php
│   │   ├── ListingResponseBuilder.php
│   │   └── UserApiTokenController.php
│   ├── App/
│   │   ├── AppVersion.php
│   │   ├── Application.php
│   │   ├── HomeController.php
│   │   ├── MailNotification.php
│   │   ├── MetaController.php
│   │   ├── Model.php
│   │   ├── Providers/
│   │   │   ├── AppServiceProvider.php
│   │   │   ├── AuthServiceProvider.php
│   │   │   ├── EventServiceProvider.php
│   │   │   ├── RouteServiceProvider.php
│   │   │   ├── ThemeServiceProvider.php
│   │   │   ├── TranslationServiceProvider.php
│   │   │   ├── ValidationRuleServiceProvider.php
│   │   │   └── ViewTweaksServiceProvider.php
│   │   ├── PwaManifestBuilder.php
│   │   ├── SluggableInterface.php
│   │   ├── SystemApiController.php
│   │   └── helpers.php
│   ├── Config/
│   │   ├── api.php
│   │   ├── app.php
│   │   ├── auth.php
│   │   ├── cache.php
│   │   ├── clockwork.php
│   │   ├── database.php
│   │   ├── debugbar.php
│   │   ├── exports.php
│   │   ├── filesystems.php
│   │   ├── hashing.php
│   │   ├── logging.php
│   │   ├── mail.php
│   │   ├── oidc.php
│   │   ├── queue.php
│   │   ├── saml2.php
│   │   ├── services.php
│   │   ├── session.php
│   │   ├── setting-defaults.php
│   │   └── view.php
│   ├── Console/
│   │   ├── Commands/
│   │   │   ├── AssignSortRuleCommand.php
│   │   │   ├── CleanupImagesCommand.php
│   │   │   ├── ClearActivityCommand.php
│   │   │   ├── ClearRevisionsCommand.php
│   │   │   ├── ClearViewsCommand.php
│   │   │   ├── CopyShelfPermissionsCommand.php
│   │   │   ├── CreateAdminCommand.php
│   │   │   ├── DeleteUsersCommand.php
│   │   │   ├── HandlesSingleUser.php
│   │   │   ├── InstallModuleCommand.php
│   │   │   ├── RefreshAvatarCommand.php
│   │   │   ├── RegeneratePermissionsCommand.php
│   │   │   ├── RegenerateReferencesCommand.php
│   │   │   ├── RegenerateSearchCommand.php
│   │   │   ├── ResetMfaCommand.php
│   │   │   ├── UpdateUrlCommand.php
│   │   │   └── UpgradeDatabaseEncodingCommand.php
│   │   └── Kernel.php
│   ├── Entities/
│   │   ├── BreadcrumbsViewComposer.php
│   │   ├── Controllers/
│   │   │   ├── BookApiController.php
│   │   │   ├── BookController.php
│   │   │   ├── BookshelfApiController.php
│   │   │   ├── BookshelfController.php
│   │   │   ├── ChapterApiController.php
│   │   │   ├── ChapterController.php
│   │   │   ├── PageApiController.php
│   │   │   ├── PageController.php
│   │   │   ├── PageRevisionController.php
│   │   │   ├── PageTemplateController.php
│   │   │   ├── RecycleBinApiController.php
│   │   │   └── RecycleBinController.php
│   │   ├── EntityExistsRule.php
│   │   ├── EntityProvider.php
│   │   ├── Models/
│   │   │   ├── Book.php
│   │   │   ├── BookChild.php
│   │   │   ├── Bookshelf.php
│   │   │   ├── Chapter.php
│   │   │   ├── ContainerTrait.php
│   │   │   ├── DeletableInterface.php
│   │   │   ├── Deletion.php
│   │   │   ├── Entity.php
│   │   │   ├── EntityContainerData.php
│   │   │   ├── EntityPageData.php
│   │   │   ├── EntityQueryBuilder.php
│   │   │   ├── EntityScope.php
│   │   │   ├── EntityTable.php
│   │   │   ├── HasCoverInterface.php
│   │   │   ├── HasDefaultTemplateInterface.php
│   │   │   ├── HasDescriptionInterface.php
│   │   │   ├── Page.php
│   │   │   ├── PageRevision.php
│   │   │   └── SlugHistory.php
│   │   ├── Queries/
│   │   │   ├── BookQueries.php
│   │   │   ├── BookshelfQueries.php
│   │   │   ├── ChapterQueries.php
│   │   │   ├── EntityQueries.php
│   │   │   ├── PageQueries.php
│   │   │   ├── PageRevisionQueries.php
│   │   │   ├── ProvidesEntityQueries.php
│   │   │   ├── QueryPopular.php
│   │   │   ├── QueryRecentlyViewed.php
│   │   │   └── QueryTopFavourites.php
│   │   ├── Repos/
│   │   │   ├── BaseRepo.php
│   │   │   ├── BookRepo.php
│   │   │   ├── BookshelfRepo.php
│   │   │   ├── ChapterRepo.php
│   │   │   ├── DeletionRepo.php
│   │   │   ├── PageRepo.php
│   │   │   └── RevisionRepo.php
│   │   └── Tools/
│   │       ├── BookContents.php
│   │       ├── Cloner.php
│   │       ├── EntityCover.php
│   │       ├── EntityDefaultTemplate.php
│   │       ├── EntityHtmlDescription.php
│   │       ├── EntityHydrator.php
│   │       ├── HierarchyTransformer.php
│   │       ├── Markdown/
│   │       │   ├── CheckboxConverter.php
│   │       │   ├── CustomDivConverter.php
│   │       │   ├── CustomImageConverter.php
│   │       │   ├── CustomListItemRenderer.php
│   │       │   ├── CustomParagraphConverter.php
│   │       │   ├── CustomStrikeThroughExtension.php
│   │       │   ├── CustomStrikethroughRenderer.php
│   │       │   ├── HtmlToMarkdown.php
│   │       │   ├── MarkdownToHtml.php
│   │       │   └── SpacedTagFallbackConverter.php
│   │       ├── MixedEntityListLoader.php
│   │       ├── MixedEntityRequestHelper.php
│   │       ├── NextPreviousContentLocator.php
│   │       ├── PageContent.php
│   │       ├── PageEditActivity.php
│   │       ├── PageEditorData.php
│   │       ├── PageEditorType.php
│   │       ├── PageIncludeContent.php
│   │       ├── PageIncludeParser.php
│   │       ├── PageIncludeTag.php
│   │       ├── ParentChanger.php
│   │       ├── PermissionsUpdater.php
│   │       ├── ShelfContext.php
│   │       ├── SiblingFetcher.php
│   │       ├── SlugGenerator.php
│   │       ├── SlugHistory.php
│   │       └── TrashCan.php
│   ├── Exceptions/
│   │   ├── ApiAuthException.php
│   │   ├── BookStackExceptionHandlerPage.php
│   │   ├── ConfirmationEmailException.php
│   │   ├── FileUploadException.php
│   │   ├── Handler.php
│   │   ├── HttpFetchException.php
│   │   ├── ImageUploadException.php
│   │   ├── JsonDebugException.php
│   │   ├── LdapException.php
│   │   ├── LoginAttemptEmailNeededException.php
│   │   ├── LoginAttemptException.php
│   │   ├── LoginAttemptInvalidUserException.php
│   │   ├── MoveOperationException.php
│   │   ├── NotFoundException.php
│   │   ├── NotifyException.php
│   │   ├── PdfExportException.php
│   │   ├── PermissionsException.php
│   │   ├── PrettyException.php
│   │   ├── SamlException.php
│   │   ├── SocialDriverNotConfigured.php
│   │   ├── SocialSignInAccountNotUsed.php
│   │   ├── SocialSignInException.php
│   │   ├── StoppedAuthenticationException.php
│   │   ├── ThemeException.php
│   │   ├── UserRegistrationException.php
│   │   ├── UserTokenExpiredException.php
│   │   ├── UserTokenNotFoundException.php
│   │   ├── UserUpdateException.php
│   │   ├── ZipExportException.php
│   │   ├── ZipImportException.php
│   │   └── ZipValidationException.php
│   ├── Exports/
│   │   ├── Controllers/
│   │   │   ├── BookExportApiController.php
│   │   │   ├── BookExportController.php
│   │   │   ├── ChapterExportApiController.php
│   │   │   ├── ChapterExportController.php
│   │   │   ├── ImportApiController.php
│   │   │   ├── ImportController.php
│   │   │   ├── PageExportApiController.php
│   │   │   └── PageExportController.php
│   │   ├── ExportFormatter.php
│   │   ├── Import.php
│   │   ├── ImportRepo.php
│   │   ├── PdfGenerator.php
│   │   └── ZipExports/
│   │       ├── Models/
│   │       │   ├── ZipExportAttachment.php
│   │       │   ├── ZipExportBook.php
│   │       │   ├── ZipExportChapter.php
│   │       │   ├── ZipExportImage.php
│   │       │   ├── ZipExportModel.php
│   │       │   ├── ZipExportPage.php
│   │       │   └── ZipExportTag.php
│   │       ├── ZipExportBuilder.php
│   │       ├── ZipExportFiles.php
│   │       ├── ZipExportReader.php
│   │       ├── ZipExportReferences.php
│   │       ├── ZipExportValidator.php
│   │       ├── ZipFileReferenceRule.php
│   │       ├── ZipImportReferences.php
│   │       ├── ZipImportRunner.php
│   │       ├── ZipReferenceParser.php
│   │       ├── ZipUniqueIdRule.php
│   │       └── ZipValidationHelper.php
│   ├── Facades/
│   │   ├── Activity.php
│   │   └── Theme.php
│   ├── Http/
│   │   ├── ApiController.php
│   │   ├── Controller.php
│   │   ├── DownloadResponseFactory.php
│   │   ├── HttpClientHistory.php
│   │   ├── HttpRequestService.php
│   │   ├── Kernel.php
│   │   ├── Middleware/
│   │   │   ├── ApiAuthenticate.php
│   │   │   ├── ApplyCspRules.php
│   │   │   ├── Authenticate.php
│   │   │   ├── AuthenticatedOrPendingMfa.php
│   │   │   ├── CheckEmailConfirmed.php
│   │   │   ├── CheckGuard.php
│   │   │   ├── CheckUserHasPermission.php
│   │   │   ├── EncryptCookies.php
│   │   │   ├── Localization.php
│   │   │   ├── PreventRequestsDuringMaintenance.php
│   │   │   ├── PreventResponseCaching.php
│   │   │   ├── RedirectIfAuthenticated.php
│   │   │   ├── RunThemeActions.php
│   │   │   ├── StartSessionExtended.php
│   │   │   ├── StartSessionIfCookieExists.php
│   │   │   ├── ThrottleApiRequests.php
│   │   │   ├── TrimStrings.php
│   │   │   ├── TrustHosts.php
│   │   │   ├── TrustProxies.php
│   │   │   └── VerifyCsrfToken.php
│   │   ├── RangeSupportedStream.php
│   │   └── Request.php
│   ├── Permissions/
│   │   ├── ContentPermissionApiController.php
│   │   ├── EntityPermissionEvaluator.php
│   │   ├── JointPermissionBuilder.php
│   │   ├── MassEntityPermissionEvaluator.php
│   │   ├── Models/
│   │   │   ├── EntityPermission.php
│   │   │   ├── JointPermission.php
│   │   │   └── RolePermission.php
│   │   ├── Permission.php
│   │   ├── PermissionApplicator.php
│   │   ├── PermissionFormData.php
│   │   ├── PermissionStatus.php
│   │   ├── PermissionsController.php
│   │   ├── PermissionsRepo.php
│   │   └── SimpleEntityData.php
│   ├── References/
│   │   ├── CrossLinkParser.php
│   │   ├── ModelResolvers/
│   │   │   ├── AttachmentModelResolver.php
│   │   │   ├── BookLinkModelResolver.php
│   │   │   ├── BookshelfLinkModelResolver.php
│   │   │   ├── ChapterLinkModelResolver.php
│   │   │   ├── CrossLinkModelResolver.php
│   │   │   ├── ImageModelResolver.php
│   │   │   ├── PageLinkModelResolver.php
│   │   │   └── PagePermalinkModelResolver.php
│   │   ├── Reference.php
│   │   ├── ReferenceChangeContext.php
│   │   ├── ReferenceController.php
│   │   ├── ReferenceFetcher.php
│   │   ├── ReferenceStore.php
│   │   └── ReferenceUpdater.php
│   ├── Search/
│   │   ├── Options/
│   │   │   ├── ExactSearchOption.php
│   │   │   ├── FilterSearchOption.php
│   │   │   ├── SearchOption.php
│   │   │   ├── TagSearchOption.php
│   │   │   └── TermSearchOption.php
│   │   ├── SearchApiController.php
│   │   ├── SearchController.php
│   │   ├── SearchIndex.php
│   │   ├── SearchOptionSet.php
│   │   ├── SearchOptions.php
│   │   ├── SearchResultsFormatter.php
│   │   ├── SearchRunner.php
│   │   ├── SearchTerm.php
│   │   └── SearchTextTokenizer.php
│   ├── Settings/
│   │   ├── AppSettingsStore.php
│   │   ├── MaintenanceController.php
│   │   ├── Setting.php
│   │   ├── SettingController.php
│   │   ├── SettingService.php
│   │   ├── StatusController.php
│   │   ├── TestEmailNotification.php
│   │   ├── UserNotificationPreferences.php
│   │   └── UserShortcutMap.php
│   ├── Sorting/
│   │   ├── BookSortController.php
│   │   ├── BookSortMap.php
│   │   ├── BookSortMapItem.php
│   │   ├── BookSorter.php
│   │   ├── SortRule.php
│   │   ├── SortRuleController.php
│   │   ├── SortRuleOperation.php
│   │   ├── SortSetOperationComparisons.php
│   │   └── SortUrl.php
│   ├── Theming/
│   │   ├── CustomHtmlHeadContentProvider.php
│   │   ├── ThemeController.php
│   │   ├── ThemeEvents.php
│   │   ├── ThemeModule.php
│   │   ├── ThemeModuleException.php
│   │   ├── ThemeModuleManager.php
│   │   ├── ThemeModuleZip.php
│   │   ├── ThemeService.php
│   │   └── ThemeViews.php
│   ├── Translation/
│   │   ├── FileLoader.php
│   │   ├── LocaleDefinition.php
│   │   ├── LocaleManager.php
│   │   └── MessageSelector.php
│   ├── Uploads/
│   │   ├── Attachment.php
│   │   ├── AttachmentService.php
│   │   ├── Controllers/
│   │   │   ├── AttachmentApiController.php
│   │   │   ├── AttachmentController.php
│   │   │   ├── DrawioImageController.php
│   │   │   ├── GalleryImageController.php
│   │   │   ├── ImageController.php
│   │   │   └── ImageGalleryApiController.php
│   │   ├── FaviconHandler.php
│   │   ├── FileStorage.php
│   │   ├── Image.php
│   │   ├── ImageRepo.php
│   │   ├── ImageResizer.php
│   │   ├── ImageService.php
│   │   ├── ImageStorage.php
│   │   ├── ImageStorageDisk.php
│   │   └── UserAvatars.php
│   ├── Users/
│   │   ├── Controllers/
│   │   │   ├── RoleApiController.php
│   │   │   ├── RoleController.php
│   │   │   ├── UserAccountController.php
│   │   │   ├── UserApiController.php
│   │   │   ├── UserController.php
│   │   │   ├── UserPreferencesController.php
│   │   │   ├── UserProfileController.php
│   │   │   └── UserSearchController.php
│   │   ├── Models/
│   │   │   ├── HasCreatorAndUpdater.php
│   │   │   ├── OwnableInterface.php
│   │   │   ├── Role.php
│   │   │   └── User.php
│   │   ├── Queries/
│   │   │   ├── RolesAllPaginatedAndSorted.php
│   │   │   ├── UserContentCounts.php
│   │   │   ├── UserRecentlyCreatedContent.php
│   │   │   └── UsersAllPaginatedAndSorted.php
│   │   └── UserRepo.php
│   └── Util/
│       ├── ConfiguredHtmlPurifier.php
│       ├── CspService.php
│       ├── DatabaseTransaction.php
│       ├── DateFormatter.php
│       ├── FilePathNormalizer.php
│       ├── HtmlContentFilter.php
│       ├── HtmlContentFilterConfig.php
│       ├── HtmlDescriptionFilter.php
│       ├── HtmlDocument.php
│       ├── HtmlNonceApplicator.php
│       ├── OutOfMemoryHandler.php
│       ├── SimpleListOptions.php
│       ├── SsrUrlValidator.php
│       ├── SvgIcon.php
│       └── WebSafeMimeSniffer.php
├── artisan
├── bookstack-system-cli
├── bootstrap/
│   ├── app.php
│   ├── cache/
│   │   └── .gitignore
│   └── phpstan.php
├── composer.json
├── crowdin.yml
├── database/
│   ├── .gitignore
│   ├── factories/
│   │   ├── Access/
│   │   │   ├── Mfa/
│   │   │   │   └── MfaValueFactory.php
│   │   │   └── SocialAccountFactory.php
│   │   ├── Activity/
│   │   │   └── Models/
│   │   │       ├── ActivityFactory.php
│   │   │       ├── CommentFactory.php
│   │   │       ├── FavouriteFactory.php
│   │   │       ├── TagFactory.php
│   │   │       ├── WatchFactory.php
│   │   │       ├── WebhookFactory.php
│   │   │       └── WebhookTrackedEventFactory.php
│   │   ├── Api/
│   │   │   └── ApiTokenFactory.php
│   │   ├── Entities/
│   │   │   └── Models/
│   │   │       ├── BookFactory.php
│   │   │       ├── BookshelfFactory.php
│   │   │       ├── ChapterFactory.php
│   │   │       ├── DeletionFactory.php
│   │   │       ├── PageFactory.php
│   │   │       ├── PageRevisionFactory.php
│   │   │       └── SlugHistoryFactory.php
│   │   ├── Exports/
│   │   │   └── ImportFactory.php
│   │   ├── Sorting/
│   │   │   └── SortRuleFactory.php
│   │   ├── Uploads/
│   │   │   ├── AttachmentFactory.php
│   │   │   └── ImageFactory.php
│   │   └── Users/
│   │       └── Models/
│   │           ├── RoleFactory.php
│   │           └── UserFactory.php
│   ├── migrations/
│   │   ├── .gitkeep
│   │   ├── 2014_10_12_000000_create_users_table.php
│   │   ├── 2014_10_12_100000_create_password_resets_table.php
│   │   ├── 2015_07_12_114933_create_books_table.php
│   │   ├── 2015_07_12_190027_create_pages_table.php
│   │   ├── 2015_07_13_172121_create_images_table.php
│   │   ├── 2015_07_27_172342_create_chapters_table.php
│   │   ├── 2015_08_08_200447_add_users_to_entities.php
│   │   ├── 2015_08_09_093534_create_page_revisions_table.php
│   │   ├── 2015_08_16_142133_create_activities_table.php
│   │   ├── 2015_08_29_105422_add_roles_and_permissions.php
│   │   ├── 2015_08_30_125859_create_settings_table.php
│   │   ├── 2015_08_31_175240_add_search_indexes.php
│   │   ├── 2015_09_04_165821_create_social_accounts_table.php
│   │   ├── 2015_09_05_164707_add_email_confirmation_table.php
│   │   ├── 2015_11_21_145609_create_views_table.php
│   │   ├── 2015_11_26_221857_add_entity_indexes.php
│   │   ├── 2015_12_05_145049_fulltext_weighting.php
│   │   ├── 2015_12_07_195238_add_image_upload_types.php
│   │   ├── 2015_12_09_195748_add_user_avatars.php
│   │   ├── 2016_01_11_210908_add_external_auth_to_users.php
│   │   ├── 2016_02_25_184030_add_slug_to_revisions.php
│   │   ├── 2016_02_27_120329_update_permissions_and_roles.php
│   │   ├── 2016_02_28_084200_add_entity_access_controls.php
│   │   ├── 2016_03_09_203143_add_page_revision_types.php
│   │   ├── 2016_03_13_082138_add_page_drafts.php
│   │   ├── 2016_03_25_123157_add_markdown_support.php
│   │   ├── 2016_04_09_100730_add_view_permissions_to_roles.php
│   │   ├── 2016_04_20_192649_create_joint_permissions_table.php
│   │   ├── 2016_05_06_185215_create_tags_table.php
│   │   ├── 2016_07_07_181521_add_summary_to_page_revisions.php
│   │   ├── 2016_09_29_101449_remove_hidden_roles.php
│   │   ├── 2016_10_09_142037_create_attachments_table.php
│   │   ├── 2017_01_21_163556_create_cache_table.php
│   │   ├── 2017_01_21_163602_create_sessions_table.php
│   │   ├── 2017_03_19_091553_create_search_index_table.php
│   │   ├── 2017_04_20_185112_add_revision_counts.php
│   │   ├── 2017_07_02_152834_update_db_encoding_to_ut8mb4.php
│   │   ├── 2017_08_01_130541_create_comments_table.php
│   │   ├── 2017_08_29_102650_add_cover_image_display.php
│   │   ├── 2018_07_15_173514_add_role_external_auth_id.php
│   │   ├── 2018_08_04_115700_create_bookshelves_table.php
│   │   ├── 2019_07_07_112515_add_template_support.php
│   │   ├── 2019_08_17_140214_add_user_invites_table.php
│   │   ├── 2019_12_29_120917_add_api_auth.php
│   │   ├── 2020_08_04_111754_drop_joint_permissions_id.php
│   │   ├── 2020_08_04_131052_remove_role_name_field.php
│   │   ├── 2020_09_19_094251_add_activity_indexes.php
│   │   ├── 2020_09_27_210059_add_entity_soft_deletes.php
│   │   ├── 2020_09_27_210528_create_deletions_table.php
│   │   ├── 2020_11_07_232321_simplify_activities_table.php
│   │   ├── 2020_12_30_173528_add_owned_by_field_to_entities.php
│   │   ├── 2021_01_30_225441_add_settings_type_column.php
│   │   ├── 2021_03_08_215138_add_user_slug.php
│   │   ├── 2021_05_15_173110_create_favourites_table.php
│   │   ├── 2021_06_30_173111_create_mfa_values_table.php
│   │   ├── 2021_07_03_085038_add_mfa_enforced_to_roles_table.php
│   │   ├── 2021_08_28_161743_add_export_role_permission.php
│   │   ├── 2021_09_26_044614_add_activities_ip_column.php
│   │   ├── 2021_11_26_070438_add_index_for_user_ip.php
│   │   ├── 2021_12_07_111343_create_webhooks_table.php
│   │   ├── 2021_12_13_152024_create_jobs_table.php
│   │   ├── 2021_12_13_152120_create_failed_jobs_table.php
│   │   ├── 2022_01_03_154041_add_webhooks_timeout_error_columns.php
│   │   ├── 2022_04_17_101741_add_editor_change_field_and_permission.php
│   │   ├── 2022_04_25_140741_update_polymorphic_types.php
│   │   ├── 2022_07_16_170051_drop_joint_permission_type.php
│   │   ├── 2022_08_17_092941_create_references_table.php
│   │   ├── 2022_09_02_082910_fix_shelf_cover_image_types.php
│   │   ├── 2022_10_07_091406_flatten_entity_permissions_table.php
│   │   ├── 2022_10_08_104202_drop_entity_restricted_field.php
│   │   ├── 2023_01_24_104625_refactor_joint_permissions_storage.php
│   │   ├── 2023_01_28_141230_copy_color_settings_for_dark_mode.php
│   │   ├── 2023_02_20_093655_increase_attachments_path_length.php
│   │   ├── 2023_02_23_200227_add_updated_at_index_to_pages.php
│   │   ├── 2023_06_10_071823_remove_guest_user_secondary_roles.php
│   │   ├── 2023_06_25_181952_remove_bookshelf_create_entity_permissions.php
│   │   ├── 2023_07_25_124945_add_receive_notifications_role_permissions.php
│   │   ├── 2023_07_31_104430_create_watches_table.php
│   │   ├── 2023_08_21_174248_increase_cache_size.php
│   │   ├── 2023_12_02_104541_add_default_template_to_books.php
│   │   ├── 2023_12_17_140913_add_description_html_to_entities.php
│   │   ├── 2024_01_01_104542_add_default_template_to_chapters.php
│   │   ├── 2024_02_04_141358_add_views_updated_index.php
│   │   ├── 2024_05_04_154409_rename_activity_relation_columns.php
│   │   ├── 2024_09_29_140340_ensure_editor_value_set.php
│   │   ├── 2024_10_29_114420_add_import_role_permission.php
│   │   ├── 2024_11_02_160700_create_imports_table.php
│   │   ├── 2024_11_27_171039_add_instance_id_setting.php
│   │   ├── 2025_01_29_180933_create_sort_rules_table.php
│   │   ├── 2025_02_05_150842_add_sort_rule_id_to_books.php
│   │   ├── 2025_04_18_215145_add_content_refs_and_archived_to_comments.php
│   │   ├── 2025_09_02_111542_remove_unused_columns.php
│   │   ├── 2025_09_15_132850_create_entities_table.php
│   │   ├── 2025_09_15_134701_migrate_entity_data.php
│   │   ├── 2025_09_15_134751_update_entity_relation_columns.php
│   │   ├── 2025_09_15_134813_drop_old_entity_tables.php
│   │   ├── 2025_10_18_163331_clean_user_id_references.php
│   │   ├── 2025_10_22_134507_update_comments_relation_field_names.php
│   │   ├── 2025_11_23_161812_create_slug_history_table.php
│   │   ├── 2025_12_15_140219_create_mention_history_table.php
│   │   └── 2025_12_19_103417_add_views_viewable_type_index.php
│   └── seeders/
│       ├── .gitkeep
│       ├── DatabaseSeeder.php
│       ├── DummyContentSeeder.php
│       └── LargeContentSeeder.php
├── dev/
│   ├── api/
│   │   ├── requests/
│   │   │   ├── attachments-create.json
│   │   │   ├── attachments-update.json
│   │   │   ├── books-create.json
│   │   │   ├── books-update.json
│   │   │   ├── chapters-create.json
│   │   │   ├── chapters-update.json
│   │   │   ├── comments-create.json
│   │   │   ├── comments-update.json
│   │   │   ├── content-permissions-update.json
│   │   │   ├── image-gallery-readDataForUrl.http
│   │   │   ├── image-gallery-update.json
│   │   │   ├── imports-run.json
│   │   │   ├── pages-create.json
│   │   │   ├── pages-update.json
│   │   │   ├── roles-create.json
│   │   │   ├── roles-update.json
│   │   │   ├── search-all.http
│   │   │   ├── shelves-create.json
│   │   │   ├── shelves-update.json
│   │   │   ├── users-create.json
│   │   │   ├── users-delete.json
│   │   │   └── users-update.json
│   │   └── responses/
│   │       ├── attachments-create.json
│   │       ├── attachments-list.json
│   │       ├── attachments-read.json
│   │       ├── attachments-update.json
│   │       ├── audit-log-list.json
│   │       ├── books-create.json
│   │       ├── books-list.json
│   │       ├── books-read.json
│   │       ├── books-update.json
│   │       ├── chapters-create.json
│   │       ├── chapters-list.json
│   │       ├── chapters-read.json
│   │       ├── chapters-update.json
│   │       ├── comments-create.json
│   │       ├── comments-list.json
│   │       ├── comments-read.json
│   │       ├── comments-update.json
│   │       ├── content-permissions-read.json
│   │       ├── content-permissions-update.json
│   │       ├── image-gallery-create.json
│   │       ├── image-gallery-list.json
│   │       ├── image-gallery-read.json
│   │       ├── image-gallery-update.json
│   │       ├── imports-create.json
│   │       ├── imports-list.json
│   │       ├── imports-read.json
│   │       ├── imports-run.json
│   │       ├── pages-create.json
│   │       ├── pages-list.json
│   │       ├── pages-read.json
│   │       ├── pages-update.json
│   │       ├── recycle-bin-destroy.json
│   │       ├── recycle-bin-list.json
│   │       ├── recycle-bin-restore.json
│   │       ├── roles-create.json
│   │       ├── roles-list.json
│   │       ├── roles-read.json
│   │       ├── roles-update.json
│   │       ├── search-all.json
│   │       ├── shelves-create.json
│   │       ├── shelves-list.json
│   │       ├── shelves-read.json
│   │       ├── shelves-update.json
│   │       ├── system-read.json
│   │       ├── users-create.json
│   │       ├── users-list.json
│   │       ├── users-read.json
│   │       └── users-update.json
│   ├── build/
│   │   ├── esbuild.mjs
│   │   ├── livereload.js
│   │   └── svg-blank-transform.js
│   ├── checksums/
│   │   ├── .gitignore
│   │   └── vendor
│   ├── docker/
│   │   ├── Dockerfile
│   │   ├── db-testing/
│   │   │   ├── Dockerfile
│   │   │   ├── readme.md
│   │   │   └── run.sh
│   │   ├── entrypoint.app.sh
│   │   ├── entrypoint.node.sh
│   │   └── php/
│   │       └── conf.d/
│   │           └── xdebug.ini
│   ├── docs/
│   │   ├── development.md
│   │   ├── javascript-code.md
│   │   ├── javascript-public-events.md
│   │   ├── logical-theme-system.md
│   │   ├── permission-scenario-testing.md
│   │   ├── php-testing.md
│   │   ├── portable-zip-file-format.md
│   │   ├── release-process.md
│   │   ├── theme-system-modules.md
│   │   ├── visual-theme-system.md
│   │   └── wysiwyg-js-api.md
│   └── licensing/
│       ├── gen-js-licenses
│       ├── gen-licenses-shared.php
│       ├── gen-php-licenses
│       ├── js-library-licenses.txt
│       └── php-library-licenses.txt
├── docker-compose.yml
├── eslint.config.mjs
├── jest.config.ts
├── lang/
│   ├── ar/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── bg/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── bn/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── bs/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ca/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── cs/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── cy/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── da/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── de/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── de_informal/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── el/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── en/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── es/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── es_AR/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── et/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── eu/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── fa/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── fi/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── fr/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── he/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── hr/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── hu/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── id/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── is/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── it/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ja/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ka/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ko/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ku/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── lt/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── lv/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── nb/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ne/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── nl/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── nn/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── pl/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── pt/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── pt_BR/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ro/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── ru/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── sk/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── sl/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── sq/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── sr/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── sv/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── tk/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── tr/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── uk/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── uz/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── vi/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   ├── zh_CN/
│   │   ├── activities.php
│   │   ├── auth.php
│   │   ├── common.php
│   │   ├── components.php
│   │   ├── editor.php
│   │   ├── entities.php
│   │   ├── errors.php
│   │   ├── notifications.php
│   │   ├── pagination.php
│   │   ├── passwords.php
│   │   ├── preferences.php
│   │   ├── settings.php
│   │   └── validation.php
│   └── zh_TW/
│       ├── activities.php
│       ├── auth.php
│       ├── common.php
│       ├── components.php
│       ├── editor.php
│       ├── entities.php
│       ├── errors.php
│       ├── notifications.php
│       ├── pagination.php
│       ├── passwords.php
│       ├── preferences.php
│       ├── settings.php
│       └── validation.php
├── package.json
├── phpcs.xml
├── phpstan.neon.dist
├── phpunit.xml
├── public/
│   ├── .htaccess
│   ├── index.php
│   ├── libs/
│   │   └── tinymce/
│   │       ├── langs/
│   │       │   └── README.md
│   │       ├── license.txt
│   │       ├── skins/
│   │       │   ├── content/
│   │       │   │   ├── dark/
│   │       │   │   │   └── content.js
│   │       │   │   ├── default/
│   │       │   │   │   └── content.js
│   │       │   │   ├── document/
│   │       │   │   │   └── content.js
│   │       │   │   ├── tinymce-5/
│   │       │   │   │   └── content.js
│   │       │   │   ├── tinymce-5-dark/
│   │       │   │   │   └── content.js
│   │       │   │   └── writer/
│   │       │   │       └── content.js
│   │       │   └── ui/
│   │       │       ├── tinymce-5/
│   │       │       │   ├── content.inline.js
│   │       │       │   ├── content.js
│   │       │       │   ├── skin.js
│   │       │       │   └── skin.shadowdom.js
│   │       │       └── tinymce-5-dark/
│   │       │           ├── content.inline.js
│   │       │           ├── content.js
│   │       │           ├── skin.js
│   │       │           └── skin.shadowdom.js
│   │       └── tinymce.d.ts
│   ├── uploads/
│   │   ├── .gitignore
│   │   └── .htaccess
│   └── web.config
├── readme.md
├── resources/
│   ├── js/
│   │   ├── app.ts
│   │   ├── code/
│   │   │   ├── index.mjs
│   │   │   ├── languages.js
│   │   │   ├── legacy-modes.mjs
│   │   │   ├── setups.js
│   │   │   ├── simple-editor-interface.js
│   │   │   ├── themes.js
│   │   │   └── views.js
│   │   ├── components/
│   │   │   ├── add-remove-rows.js
│   │   │   ├── ajax-delete-row.ts
│   │   │   ├── ajax-form.js
│   │   │   ├── api-nav.ts
│   │   │   ├── attachments-list.js
│   │   │   ├── attachments.js
│   │   │   ├── auto-submit.js
│   │   │   ├── auto-suggest.js
│   │   │   ├── back-to-top.js
│   │   │   ├── book-sort.js
│   │   │   ├── chapter-contents.js
│   │   │   ├── code-editor.js
│   │   │   ├── code-highlighter.js
│   │   │   ├── code-textarea.js
│   │   │   ├── collapsible.js
│   │   │   ├── component.js
│   │   │   ├── confirm-dialog.js
│   │   │   ├── custom-checkbox.js
│   │   │   ├── details-highlighter.js
│   │   │   ├── dropdown-search.js
│   │   │   ├── dropdown.js
│   │   │   ├── dropzone.js
│   │   │   ├── editor-toolbox.ts
│   │   │   ├── entity-permissions.js
│   │   │   ├── entity-search.js
│   │   │   ├── entity-selector-popup.ts
│   │   │   ├── entity-selector.ts
│   │   │   ├── event-emit-select.js
│   │   │   ├── expand-toggle.js
│   │   │   ├── global-search.js
│   │   │   ├── header-mobile-toggle.js
│   │   │   ├── image-manager.js
│   │   │   ├── image-picker.js
│   │   │   ├── index.ts
│   │   │   ├── list-sort-control.js
│   │   │   ├── loading-button.ts
│   │   │   ├── markdown-editor.js
│   │   │   ├── new-user-password.js
│   │   │   ├── notification.js
│   │   │   ├── optional-input.js
│   │   │   ├── page-comment-reference.ts
│   │   │   ├── page-comment.ts
│   │   │   ├── page-comments.ts
│   │   │   ├── page-display.js
│   │   │   ├── page-editor.js
│   │   │   ├── page-picker.js
│   │   │   ├── permissions-table.js
│   │   │   ├── pointer.ts
│   │   │   ├── popup.js
│   │   │   ├── setting-app-color-scheme.js
│   │   │   ├── setting-color-picker.js
│   │   │   ├── setting-homepage-control.js
│   │   │   ├── shelf-sort.js
│   │   │   ├── shortcut-input.js
│   │   │   ├── shortcuts.js
│   │   │   ├── sort-rule-manager.ts
│   │   │   ├── sortable-list.js
│   │   │   ├── submit-on-change.js
│   │   │   ├── tabs.ts
│   │   │   ├── tag-manager.js
│   │   │   ├── template-manager.js
│   │   │   ├── toggle-switch.js
│   │   │   ├── tri-layout.ts
│   │   │   ├── user-select.js
│   │   │   ├── webhook-events.js
│   │   │   ├── wysiwyg-editor-tinymce.js
│   │   │   ├── wysiwyg-editor.js
│   │   │   └── wysiwyg-input.ts
│   │   ├── custom.d.ts
│   │   ├── global.d.ts
│   │   ├── markdown/
│   │   │   ├── actions.ts
│   │   │   ├── codemirror.ts
│   │   │   ├── common-events.ts
│   │   │   ├── display.ts
│   │   │   ├── dom-handlers.ts
│   │   │   ├── index.mts
│   │   │   ├── inputs/
│   │   │   │   ├── codemirror.ts
│   │   │   │   ├── interface.ts
│   │   │   │   └── textarea.ts
│   │   │   ├── markdown.ts
│   │   │   ├── settings.ts
│   │   │   └── shortcuts.ts
│   │   ├── services/
│   │   │   ├── __tests__/
│   │   │   │   └── translations.test.ts
│   │   │   ├── animations.ts
│   │   │   ├── clipboard.ts
│   │   │   ├── components.ts
│   │   │   ├── dates.ts
│   │   │   ├── dom.ts
│   │   │   ├── drawio.ts
│   │   │   ├── dual-lists.ts
│   │   │   ├── events.ts
│   │   │   ├── http.ts
│   │   │   ├── keyboard-navigation.ts
│   │   │   ├── store.ts
│   │   │   ├── text.ts
│   │   │   ├── translations.ts
│   │   │   ├── util.ts
│   │   │   └── vdom.ts
│   │   ├── wysiwyg/
│   │   │   ├── api/
│   │   │   │   ├── __tests__/
│   │   │   │   │   ├── api-test-utils.ts
│   │   │   │   │   ├── content.test.ts
│   │   │   │   │   └── ui.test.ts
│   │   │   │   ├── api.ts
│   │   │   │   ├── content.ts
│   │   │   │   └── ui.ts
│   │   │   ├── index.ts
│   │   │   ├── lexical/
│   │   │   │   ├── ORIGINAL-LEXICAL-LICENSE
│   │   │   │   ├── clipboard/
│   │   │   │   │   ├── clipboard.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── core/
│   │   │   │   │   ├── LexicalCommands.ts
│   │   │   │   │   ├── LexicalConstants.ts
│   │   │   │   │   ├── LexicalEditor.ts
│   │   │   │   │   ├── LexicalEditorState.ts
│   │   │   │   │   ├── LexicalEvents.ts
│   │   │   │   │   ├── LexicalGC.ts
│   │   │   │   │   ├── LexicalMutations.ts
│   │   │   │   │   ├── LexicalNode.ts
│   │   │   │   │   ├── LexicalNormalization.ts
│   │   │   │   │   ├── LexicalReconciler.ts
│   │   │   │   │   ├── LexicalSelection.ts
│   │   │   │   │   ├── LexicalUpdates.ts
│   │   │   │   │   ├── LexicalUtils.ts
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   ├── unit/
│   │   │   │   │   │   │   ├── HTMLCopyAndPaste.test.ts
│   │   │   │   │   │   │   ├── LexicalEditor.test.ts
│   │   │   │   │   │   │   ├── LexicalEditorState.test.ts
│   │   │   │   │   │   │   ├── LexicalNode.test.ts
│   │   │   │   │   │   │   ├── LexicalNormalization.test.ts
│   │   │   │   │   │   │   ├── LexicalSelection.test.ts
│   │   │   │   │   │   │   ├── LexicalSerialization.test.ts
│   │   │   │   │   │   │   └── LexicalUtils.test.ts
│   │   │   │   │   │   └── utils/
│   │   │   │   │   │       └── index.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── nodes/
│   │   │   │   │   │   ├── ArtificialNode.ts
│   │   │   │   │   │   ├── CommonBlockNode.ts
│   │   │   │   │   │   ├── LexicalDecoratorNode.ts
│   │   │   │   │   │   ├── LexicalElementNode.ts
│   │   │   │   │   │   ├── LexicalLineBreakNode.ts
│   │   │   │   │   │   ├── LexicalParagraphNode.ts
│   │   │   │   │   │   ├── LexicalRootNode.ts
│   │   │   │   │   │   ├── LexicalTabNode.ts
│   │   │   │   │   │   ├── LexicalTextNode.ts
│   │   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   │   └── unit/
│   │   │   │   │   │   │       ├── LexicalElementNode.test.ts
│   │   │   │   │   │   │       ├── LexicalGC.test.ts
│   │   │   │   │   │   │       ├── LexicalLineBreakNode.test.ts
│   │   │   │   │   │   │       ├── LexicalParagraphNode.test.ts
│   │   │   │   │   │   │       ├── LexicalRootNode.test.ts
│   │   │   │   │   │   │       ├── LexicalTabNode.test.ts
│   │   │   │   │   │   │       └── LexicalTextNode.test.ts
│   │   │   │   │   │   └── common.ts
│   │   │   │   │   └── shared/
│   │   │   │   │       ├── __mocks__/
│   │   │   │   │       │   └── invariant.ts
│   │   │   │   │       ├── canUseDOM.ts
│   │   │   │   │       ├── caretFromPoint.ts
│   │   │   │   │       ├── environment.ts
│   │   │   │   │       ├── invariant.ts
│   │   │   │   │       ├── normalizeClassNames.ts
│   │   │   │   │       ├── simpleDiffWithCursor.ts
│   │   │   │   │       └── warnOnlyOnce.ts
│   │   │   │   ├── headless/
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       └── LexicalHeadlessEditor.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── history/
│   │   │   │   │   └── index.ts
│   │   │   │   ├── html/
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       └── LexicalHtml.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── link/
│   │   │   │   │   ├── LexicalMentionNode.ts
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       ├── LexicalAutoLinkNode.test.ts
│   │   │   │   │   │       └── LexicalLinkNode.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── list/
│   │   │   │   │   ├── LexicalListItemNode.ts
│   │   │   │   │   ├── LexicalListNode.ts
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       ├── LexicalListItemNode.test.ts
│   │   │   │   │   │       ├── LexicalListNode.test.ts
│   │   │   │   │   │       └── utils.test.ts
│   │   │   │   │   ├── formatList.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── readme.md
│   │   │   │   ├── rich-text/
│   │   │   │   │   ├── LexicalCalloutNode.ts
│   │   │   │   │   ├── LexicalCodeBlockNode.ts
│   │   │   │   │   ├── LexicalDetailsNode.ts
│   │   │   │   │   ├── LexicalDiagramNode.ts
│   │   │   │   │   ├── LexicalHeadingNode.ts
│   │   │   │   │   ├── LexicalHorizontalRuleNode.ts
│   │   │   │   │   ├── LexicalImageNode.ts
│   │   │   │   │   ├── LexicalMediaNode.ts
│   │   │   │   │   ├── LexicalQuoteNode.ts
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       ├── LexicalDetailsNode.test.ts
│   │   │   │   │   │       ├── LexicalHeadingNode.test.ts
│   │   │   │   │   │       ├── LexicalMediaNode.test.ts
│   │   │   │   │   │       └── LexicalQuoteNode.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── selection/
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   ├── unit/
│   │   │   │   │   │   │   ├── LexicalSelection.test.ts
│   │   │   │   │   │   │   └── LexicalSelectionHelpers.test.ts
│   │   │   │   │   │   └── utils/
│   │   │   │   │   │       └── index.ts
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── lexical-node.ts
│   │   │   │   │   ├── range-selection.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── table/
│   │   │   │   │   ├── LexicalCaptionNode.ts
│   │   │   │   │   ├── LexicalTableCellNode.ts
│   │   │   │   │   ├── LexicalTableCommands.ts
│   │   │   │   │   ├── LexicalTableNode.ts
│   │   │   │   │   ├── LexicalTableObserver.ts
│   │   │   │   │   ├── LexicalTableRowNode.ts
│   │   │   │   │   ├── LexicalTableSelection.ts
│   │   │   │   │   ├── LexicalTableSelectionHelpers.ts
│   │   │   │   │   ├── LexicalTableUtils.ts
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       ├── LexicalTableCellNode.test.ts
│   │   │   │   │   │       ├── LexicalTableNode.test.ts
│   │   │   │   │   │       ├── LexicalTableRowNode.test.ts
│   │   │   │   │   │       └── LexicalTableSelection.test.ts
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── utils/
│   │   │   │   │   ├── __tests__/
│   │   │   │   │   │   └── unit/
│   │   │   │   │   │       ├── LexicalElementHelpers.test.ts
│   │   │   │   │   │       ├── LexicalEventHelpers.test.ts
│   │   │   │   │   │       ├── LexicalNodeHelpers.test.ts
│   │   │   │   │   │       ├── LexicalRootHelpers.test.ts
│   │   │   │   │   │       ├── LexicalUtilsKlassEqual.test.ts
│   │   │   │   │   │       ├── LexicalUtilsSplitNode.test.ts
│   │   │   │   │   │       ├── LexlcaiUtilsInsertNodeToNearestRoot.test.ts
│   │   │   │   │   │       └── mergeRegister.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── markSelection.ts
│   │   │   │   │   ├── mergeRegister.ts
│   │   │   │   │   ├── positionNodeOnRange.ts
│   │   │   │   │   └── px.ts
│   │   │   │   └── yjs/
│   │   │   │       ├── Bindings.ts
│   │   │   │       ├── CollabDecoratorNode.ts
│   │   │   │       ├── CollabElementNode.ts
│   │   │   │       ├── CollabLineBreakNode.ts
│   │   │   │       ├── CollabTextNode.ts
│   │   │   │       ├── SyncCursors.ts
│   │   │   │       ├── SyncEditorStates.ts
│   │   │   │       ├── Utils.ts
│   │   │   │       ├── index.ts
│   │   │   │       └── types.ts
│   │   │   ├── nodes.ts
│   │   │   ├── services/
│   │   │   │   ├── __tests__/
│   │   │   │   │   ├── auto-links.test.ts
│   │   │   │   │   ├── keyboard-handling.test.ts
│   │   │   │   │   └── mouse-handling.test.ts
│   │   │   │   ├── auto-links.ts
│   │   │   │   ├── common-events.ts
│   │   │   │   ├── drop-paste-handling.ts
│   │   │   │   ├── keyboard-handling.ts
│   │   │   │   ├── mentions.ts
│   │   │   │   ├── mouse-handling.ts
│   │   │   │   ├── selection-handling.ts
│   │   │   │   └── shortcuts.ts
│   │   │   ├── testing.md
│   │   │   ├── ui/
│   │   │   │   ├── decorators/
│   │   │   │   │   ├── CodeBlockDecorator.ts
│   │   │   │   │   ├── DiagramDecorator.ts
│   │   │   │   │   └── MentionDecorator.ts
│   │   │   │   ├── defaults/
│   │   │   │   │   ├── buttons/
│   │   │   │   │   │   ├── alignments.ts
│   │   │   │   │   │   ├── block-formats.ts
│   │   │   │   │   │   ├── controls.ts
│   │   │   │   │   │   ├── inline-formats.ts
│   │   │   │   │   │   ├── lists.ts
│   │   │   │   │   │   ├── objects.ts
│   │   │   │   │   │   └── tables.ts
│   │   │   │   │   ├── forms/
│   │   │   │   │   │   ├── controls.ts
│   │   │   │   │   │   ├── objects.ts
│   │   │   │   │   │   └── tables.ts
│   │   │   │   │   ├── modals.ts
│   │   │   │   │   └── toolbars.ts
│   │   │   │   ├── framework/
│   │   │   │   │   ├── blocks/
│   │   │   │   │   │   ├── action-field.ts
│   │   │   │   │   │   ├── button-with-menu.ts
│   │   │   │   │   │   ├── color-button.ts
│   │   │   │   │   │   ├── color-field.ts
│   │   │   │   │   │   ├── color-picker.ts
│   │   │   │   │   │   ├── dropdown-button.ts
│   │   │   │   │   │   ├── external-content.ts
│   │   │   │   │   │   ├── format-menu.ts
│   │   │   │   │   │   ├── format-preview-button.ts
│   │   │   │   │   │   ├── link-field.ts
│   │   │   │   │   │   ├── menu-button.ts
│   │   │   │   │   │   ├── overflow-container.ts
│   │   │   │   │   │   ├── separator.ts
│   │   │   │   │   │   └── table-creator.ts
│   │   │   │   │   ├── buttons.ts
│   │   │   │   │   ├── core.ts
│   │   │   │   │   ├── decorator.ts
│   │   │   │   │   ├── forms.ts
│   │   │   │   │   ├── helpers/
│   │   │   │   │   │   ├── dropdowns.ts
│   │   │   │   │   │   ├── mouse-drag-tracker.ts
│   │   │   │   │   │   ├── node-resizer.ts
│   │   │   │   │   │   ├── table-resizer.ts
│   │   │   │   │   │   ├── table-selection-handler.ts
│   │   │   │   │   │   └── task-list-handler.ts
│   │   │   │   │   ├── manager.ts
│   │   │   │   │   ├── modals.ts
│   │   │   │   │   └── toolbars.ts
│   │   │   │   └── index.ts
│   │   │   └── utils/
│   │   │       ├── __tests__/
│   │   │       │   └── lists.test.ts
│   │   │       ├── actions.ts
│   │   │       ├── details.ts
│   │   │       ├── diagrams.ts
│   │   │       ├── dom.ts
│   │   │       ├── formats.ts
│   │   │       ├── images.ts
│   │   │       ├── links.ts
│   │   │       ├── lists.ts
│   │   │       ├── node-clipboard.ts
│   │   │       ├── nodes.ts
│   │   │       ├── selection.ts
│   │   │       ├── table-copy-paste.ts
│   │   │       ├── table-map.ts
│   │   │       └── tables.ts
│   │   └── wysiwyg-tinymce/
│   │       ├── common-events.js
│   │       ├── config.js
│   │       ├── drop-paste-handling.js
│   │       ├── filters.js
│   │       ├── fixes.js
│   │       ├── icons.js
│   │       ├── plugin-codeeditor.js
│   │       ├── plugin-drawio.js
│   │       ├── plugins-about.js
│   │       ├── plugins-customhr.js
│   │       ├── plugins-details.js
│   │       ├── plugins-imagemanager.js
│   │       ├── plugins-stub.js
│   │       ├── plugins-table-additions.js
│   │       ├── plugins-tasklist.js
│   │       ├── scrolling.js
│   │       ├── shortcuts.js
│   │       ├── toolbars.js
│   │       └── util.js
│   ├── sass/
│   │   ├── _animations.scss
│   │   ├── _blocks.scss
│   │   ├── _buttons.scss
│   │   ├── _codemirror.scss
│   │   ├── _colors.scss
│   │   ├── _components.scss
│   │   ├── _content.scss
│   │   ├── _editor.scss
│   │   ├── _footer.scss
│   │   ├── _forms.scss
│   │   ├── _header.scss
│   │   ├── _html.scss
│   │   ├── _layout.scss
│   │   ├── _lists.scss
│   │   ├── _mixins.scss
│   │   ├── _opacity.scss
│   │   ├── _pages.scss
│   │   ├── _print.scss
│   │   ├── _reset.scss
│   │   ├── _spacing.scss
│   │   ├── _tables.scss
│   │   ├── _text.scss
│   │   ├── _tinymce.scss
│   │   ├── _vars.scss
│   │   ├── export-styles.scss
│   │   └── styles.scss
│   └── views/
│       ├── api-docs/
│       │   ├── index.blade.php
│       │   └── parts/
│       │       ├── endpoint.blade.php
│       │       └── getting-started.blade.php
│       ├── attachments/
│       │   ├── list.blade.php
│       │   ├── manager-edit-form.blade.php
│       │   ├── manager-link-form.blade.php
│       │   ├── manager-list.blade.php
│       │   └── manager.blade.php
│       ├── auth/
│       │   ├── invite-set-password.blade.php
│       │   ├── login-initiate.blade.php
│       │   ├── login.blade.php
│       │   ├── parts/
│       │   │   ├── login-form-ldap.blade.php
│       │   │   ├── login-form-oidc.blade.php
│       │   │   ├── login-form-saml2.blade.php
│       │   │   ├── login-form-standard.blade.php
│       │   │   ├── login-message.blade.php
│       │   │   └── register-message.blade.php
│       │   ├── passwords/
│       │   │   ├── email.blade.php
│       │   │   └── reset.blade.php
│       │   ├── register-confirm-accept.blade.php
│       │   ├── register-confirm-awaiting.blade.php
│       │   ├── register-confirm.blade.php
│       │   └── register.blade.php
│       ├── books/
│       │   ├── copy.blade.php
│       │   ├── create.blade.php
│       │   ├── delete.blade.php
│       │   ├── edit.blade.php
│       │   ├── index.blade.php
│       │   ├── parts/
│       │   │   ├── convert-to-shelf.blade.php
│       │   │   ├── form.blade.php
│       │   │   ├── index-sidebar-section-actions.blade.php
│       │   │   ├── index-sidebar-section-new.blade.php
│       │   │   ├── index-sidebar-section-popular.blade.php
│       │   │   ├── index-sidebar-section-recents.blade.php
│       │   │   ├── list-item.blade.php
│       │   │   ├── list.blade.php
│       │   │   ├── show-sidebar-section-actions.blade.php
│       │   │   ├── show-sidebar-section-activity.blade.php
│       │   │   ├── show-sidebar-section-details.blade.php
│       │   │   ├── show-sidebar-section-shelves.blade.php
│       │   │   ├── show-sidebar-section-tags.blade.php
│       │   │   ├── sort-box-actions.blade.php
│       │   │   └── sort-box.blade.php
│       │   ├── permissions.blade.php
│       │   ├── references.blade.php
│       │   ├── show.blade.php
│       │   └── sort.blade.php
│       ├── chapters/
│       │   ├── copy.blade.php
│       │   ├── create.blade.php
│       │   ├── delete.blade.php
│       │   ├── edit.blade.php
│       │   ├── move.blade.php
│       │   ├── parts/
│       │   │   ├── child-menu.blade.php
│       │   │   ├── convert-to-book.blade.php
│       │   │   ├── form.blade.php
│       │   │   ├── list-item.blade.php
│       │   │   ├── show-sidebar-section-actions.blade.php
│       │   │   ├── show-sidebar-section-details.blade.php
│       │   │   └── show-sidebar-section-tags.blade.php
│       │   ├── permissions.blade.php
│       │   ├── references.blade.php
│       │   └── show.blade.php
│       ├── comments/
│       │   ├── comment-branch.blade.php
│       │   ├── comment.blade.php
│       │   ├── comments.blade.php
│       │   └── create.blade.php
│       ├── common/
│       │   ├── activity-item.blade.php
│       │   ├── activity-list.blade.php
│       │   ├── confirm-dialog.blade.php
│       │   ├── dark-mode-toggle.blade.php
│       │   ├── detailed-listing-paginated.blade.php
│       │   ├── detailed-listing-with-more.blade.php
│       │   ├── loading-icon.blade.php
│       │   ├── sort.blade.php
│       │   └── status-indicator.blade.php
│       ├── entities/
│       │   ├── body-tag-classes.blade.php
│       │   ├── book-tree.blade.php
│       │   ├── breadcrumb-listing.blade.php
│       │   ├── breadcrumbs.blade.php
│       │   ├── copy-considerations.blade.php
│       │   ├── export-menu.blade.php
│       │   ├── favourite-action.blade.php
│       │   ├── grid-item.blade.php
│       │   ├── icon-link.blade.php
│       │   ├── list-basic.blade.php
│       │   ├── list-item-basic.blade.php
│       │   ├── list-item.blade.php
│       │   ├── list.blade.php
│       │   ├── meta.blade.php
│       │   ├── references.blade.php
│       │   ├── search-form.blade.php
│       │   ├── search-results.blade.php
│       │   ├── selector-popup.blade.php
│       │   ├── selector.blade.php
│       │   ├── sibling-navigation.blade.php
│       │   ├── tag-list.blade.php
│       │   ├── tag-manager-list.blade.php
│       │   ├── tag-manager.blade.php
│       │   ├── tag.blade.php
│       │   ├── template-selector.blade.php
│       │   ├── view-toggle.blade.php
│       │   ├── watch-action.blade.php
│       │   └── watch-controls.blade.php
│       ├── errors/
│       │   ├── 404.blade.php
│       │   ├── 500.blade.php
│       │   ├── 503.blade.php
│       │   ├── debug.blade.php
│       │   └── parts/
│       │       └── not-found-text.blade.php
│       ├── exports/
│       │   ├── book.blade.php
│       │   ├── chapter.blade.php
│       │   ├── import-show.blade.php
│       │   ├── import.blade.php
│       │   ├── page.blade.php
│       │   └── parts/
│       │       ├── book-contents-menu.blade.php
│       │       ├── chapter-contents-menu.blade.php
│       │       ├── chapter-item.blade.php
│       │       ├── custom-head.blade.php
│       │       ├── import-item.blade.php
│       │       ├── import.blade.php
│       │       ├── meta.blade.php
│       │       ├── page-item.blade.php
│       │       └── styles.blade.php
│       ├── form/
│       │   ├── checkbox.blade.php
│       │   ├── custom-checkbox.blade.php
│       │   ├── date.blade.php
│       │   ├── description-html-input.blade.php
│       │   ├── editor-translations.blade.php
│       │   ├── entity-permissions-row.blade.php
│       │   ├── entity-permissions.blade.php
│       │   ├── errors.blade.php
│       │   ├── image-picker.blade.php
│       │   ├── number.blade.php
│       │   ├── page-picker.blade.php
│       │   ├── password.blade.php
│       │   ├── request-query-inputs.blade.php
│       │   ├── role-checkboxes.blade.php
│       │   ├── role-select.blade.php
│       │   ├── simple-dropzone.blade.php
│       │   ├── text.blade.php
│       │   ├── textarea.blade.php
│       │   ├── toggle-switch.blade.php
│       │   ├── user-mention-list.blade.php
│       │   ├── user-select-list.blade.php
│       │   └── user-select.blade.php
│       ├── help/
│       │   ├── licenses.blade.php
│       │   ├── tinymce.blade.php
│       │   └── wysiwyg.blade.php
│       ├── home/
│       │   ├── books.blade.php
│       │   ├── default.blade.php
│       │   ├── parts/
│       │   │   ├── expand-toggle.blade.php
│       │   │   └── sidebar.blade.php
│       │   ├── shelves.blade.php
│       │   └── specific-page.blade.php
│       ├── layouts/
│       │   ├── base.blade.php
│       │   ├── export.blade.php
│       │   ├── parts/
│       │   │   ├── base-body-end.blade.php
│       │   │   ├── base-body-start.blade.php
│       │   │   ├── custom-head.blade.php
│       │   │   ├── custom-styles.blade.php
│       │   │   ├── export-body-end.blade.php
│       │   │   ├── export-body-start.blade.php
│       │   │   ├── footer.blade.php
│       │   │   ├── header-links-start.blade.php
│       │   │   ├── header-links.blade.php
│       │   │   ├── header-logo.blade.php
│       │   │   ├── header-search.blade.php
│       │   │   ├── header-user-menu.blade.php
│       │   │   ├── header.blade.php
│       │   │   ├── notifications.blade.php
│       │   │   └── skip-to-content.blade.php
│       │   ├── plain.blade.php
│       │   ├── simple.blade.php
│       │   └── tri.blade.php
│       ├── mfa/
│       │   ├── backup-codes-generate.blade.php
│       │   ├── parts/
│       │   │   ├── setup-method-row.blade.php
│       │   │   ├── verify-backup_codes.blade.php
│       │   │   └── verify-totp.blade.php
│       │   ├── setup.blade.php
│       │   ├── totp-generate.blade.php
│       │   └── verify.blade.php
│       ├── misc/
│       │   ├── opensearch.blade.php
│       │   └── robots.blade.php
│       ├── pages/
│       │   ├── copy.blade.php
│       │   ├── delete.blade.php
│       │   ├── edit.blade.php
│       │   ├── guest-create.blade.php
│       │   ├── move.blade.php
│       │   ├── parts/
│       │   │   ├── code-editor.blade.php
│       │   │   ├── editor-toolbar.blade.php
│       │   │   ├── editor-toolbox.blade.php
│       │   │   ├── form.blade.php
│       │   │   ├── image-manager-form.blade.php
│       │   │   ├── image-manager-list.blade.php
│       │   │   ├── image-manager.blade.php
│       │   │   ├── list-item.blade.php
│       │   │   ├── markdown-editor.blade.php
│       │   │   ├── page-display.blade.php
│       │   │   ├── pointer.blade.php
│       │   │   ├── revisions-index-row.blade.php
│       │   │   ├── show-sidebar-section-actions.blade.php
│       │   │   ├── show-sidebar-section-attachments.blade.php
│       │   │   ├── show-sidebar-section-details.blade.php
│       │   │   ├── show-sidebar-section-page-nav.blade.php
│       │   │   ├── show-sidebar-section-tags.blade.php
│       │   │   ├── template-manager-list.blade.php
│       │   │   ├── template-manager.blade.php
│       │   │   ├── toolbox-comments.blade.php
│       │   │   ├── wysiwyg-editor-tinymce.blade.php
│       │   │   └── wysiwyg-editor.blade.php
│       │   ├── permissions.blade.php
│       │   ├── references.blade.php
│       │   ├── revision.blade.php
│       │   ├── revisions.blade.php
│       │   └── show.blade.php
│       ├── readme.md
│       ├── search/
│       │   ├── all.blade.php
│       │   └── parts/
│       │       ├── boolean-filter.blade.php
│       │       ├── date-filter.blade.php
│       │       ├── entity-selector-list.blade.php
│       │       ├── entity-suggestion-list.blade.php
│       │       ├── term-list.blade.php
│       │       └── type-filter.blade.php
│       ├── settings/
│       │   ├── audit.blade.php
│       │   ├── categories/
│       │   │   ├── customization.blade.php
│       │   │   ├── features.blade.php
│       │   │   ├── registration.blade.php
│       │   │   └── sorting.blade.php
│       │   ├── layout.blade.php
│       │   ├── maintenance.blade.php
│       │   ├── parts/
│       │   │   ├── footer-links.blade.php
│       │   │   ├── navbar.blade.php
│       │   │   ├── setting-color-picker.blade.php
│       │   │   ├── setting-color-scheme.blade.php
│       │   │   └── table-user.blade.php
│       │   ├── recycle-bin/
│       │   │   ├── destroy.blade.php
│       │   │   ├── index.blade.php
│       │   │   ├── parts/
│       │   │   │   ├── deletable-entity-list.blade.php
│       │   │   │   ├── entity-display-item.blade.php
│       │   │   │   └── recycle-bin-list-item.blade.php
│       │   │   └── restore.blade.php
│       │   ├── roles/
│       │   │   ├── create.blade.php
│       │   │   ├── delete.blade.php
│       │   │   ├── edit.blade.php
│       │   │   ├── index.blade.php
│       │   │   └── parts/
│       │   │       ├── asset-permissions-row.blade.php
│       │   │       ├── checkbox.blade.php
│       │   │       ├── form.blade.php
│       │   │       ├── related-asset-permissions-row.blade.php
│       │   │       └── roles-list-item.blade.php
│       │   ├── sort-rules/
│       │   │   ├── create.blade.php
│       │   │   ├── edit.blade.php
│       │   │   └── parts/
│       │   │       ├── form.blade.php
│       │   │       ├── operation.blade.php
│       │   │       └── sort-rule-list-item.blade.php
│       │   └── webhooks/
│       │       ├── create.blade.php
│       │       ├── delete.blade.php
│       │       ├── edit.blade.php
│       │       ├── index.blade.php
│       │       └── parts/
│       │           ├── form.blade.php
│       │           ├── format-example.blade.php
│       │           └── webhooks-list-item.blade.php
│       ├── shelves/
│       │   ├── create.blade.php
│       │   ├── delete.blade.php
│       │   ├── edit.blade.php
│       │   ├── index.blade.php
│       │   ├── parts/
│       │   │   ├── form.blade.php
│       │   │   ├── index-sidebar-section-actions.blade.php
│       │   │   ├── index-sidebar-section-new.blade.php
│       │   │   ├── index-sidebar-section-popular.blade.php
│       │   │   ├── index-sidebar-section-recents.blade.php
│       │   │   ├── list-item.blade.php
│       │   │   ├── list.blade.php
│       │   │   ├── shelf-sort-book-item.blade.php
│       │   │   ├── show-sidebar-section-actions.blade.php
│       │   │   ├── show-sidebar-section-activity.blade.php
│       │   │   ├── show-sidebar-section-details.blade.php
│       │   │   └── show-sidebar-section-tags.blade.php
│       │   ├── permissions.blade.php
│       │   ├── references.blade.php
│       │   └── show.blade.php
│       ├── tags/
│       │   ├── index.blade.php
│       │   └── parts/
│       │       └── tags-list-item.blade.php
│       ├── users/
│       │   ├── account/
│       │   │   ├── auth.blade.php
│       │   │   ├── delete.blade.php
│       │   │   ├── layout.blade.php
│       │   │   ├── notifications.blade.php
│       │   │   ├── parts/
│       │   │   │   └── shortcut-control.blade.php
│       │   │   ├── profile.blade.php
│       │   │   └── shortcuts.blade.php
│       │   ├── api-tokens/
│       │   │   ├── create.blade.php
│       │   │   ├── delete.blade.php
│       │   │   ├── edit.blade.php
│       │   │   └── parts/
│       │   │       ├── form.blade.php
│       │   │       └── list.blade.php
│       │   ├── create.blade.php
│       │   ├── delete.blade.php
│       │   ├── edit.blade.php
│       │   ├── index.blade.php
│       │   ├── parts/
│       │   │   ├── form.blade.php
│       │   │   ├── language-option-row.blade.php
│       │   │   └── users-list-item.blade.php
│       │   └── profile.blade.php
│       └── vendor/
│           ├── notifications/
│           │   ├── email-plain.blade.php
│           │   └── email.blade.php
│           └── pagination/
│               └── default.blade.php
├── routes/
│   ├── api.php
│   └── web.php
├── storage/
│   ├── app/
│   │   └── .gitignore
│   ├── backups/
│   │   └── .gitignore
│   ├── clockwork/
│   │   └── .gitignore
│   ├── fonts/
│   │   └── .gitignore
│   ├── framework/
│   │   ├── .gitignore
│   │   ├── cache/
│   │   │   └── .gitignore
│   │   ├── sessions/
│   │   │   └── .gitignore
│   │   └── views/
│   │       └── .gitignore
│   ├── logs/
│   │   └── .gitignore
│   └── uploads/
│       ├── files/
│       │   └── .gitignore
│       └── images/
│           └── .gitignore
├── tests/
│   ├── Activity/
│   │   ├── AuditLogApiTest.php
│   │   ├── AuditLogTest.php
│   │   ├── CommentDisplayTest.php
│   │   ├── CommentMentionTest.php
│   │   ├── CommentSettingTest.php
│   │   ├── CommentStoreTest.php
│   │   ├── CommentsApiTest.php
│   │   ├── MentionParserTest.php
│   │   ├── WatchTest.php
│   │   ├── WebhookCallTest.php
│   │   ├── WebhookFormatTesting.php
│   │   └── WebhookManagementTest.php
│   ├── Api/
│   │   ├── ApiAuthTest.php
│   │   ├── ApiConfigTest.php
│   │   ├── ApiDocsTest.php
│   │   ├── ApiListingTest.php
│   │   ├── AttachmentsApiTest.php
│   │   ├── BooksApiTest.php
│   │   ├── ChaptersApiTest.php
│   │   ├── ContentPermissionsApiTest.php
│   │   ├── ExportsApiTest.php
│   │   ├── ImageGalleryApiTest.php
│   │   ├── ImportsApiTest.php
│   │   ├── PagesApiTest.php
│   │   ├── RecycleBinApiTest.php
│   │   ├── RolesApiTest.php
│   │   ├── SearchApiTest.php
│   │   ├── ShelvesApiTest.php
│   │   ├── SystemApiTest.php
│   │   ├── TestsApi.php
│   │   └── UsersApiTest.php
│   ├── Auth/
│   │   ├── AuthTest.php
│   │   ├── GroupSyncServiceTest.php
│   │   ├── LdapTest.php
│   │   ├── LoginAutoInitiateTest.php
│   │   ├── MfaConfigurationTest.php
│   │   ├── MfaVerificationTest.php
│   │   ├── OidcTest.php
│   │   ├── RegistrationTest.php
│   │   ├── ResetPasswordTest.php
│   │   ├── Saml2Test.php
│   │   ├── SocialAuthTest.php
│   │   └── UserInviteTest.php
│   ├── Commands/
│   │   ├── AssignSortRuleCommandTest.php
│   │   ├── CleanupImagesCommandTest.php
│   │   ├── ClearActivityCommandTest.php
│   │   ├── ClearRevisionsCommandTest.php
│   │   ├── ClearViewsCommandTest.php
│   │   ├── CopyShelfPermissionsCommandTest.php
│   │   ├── CreateAdminCommandTest.php
│   │   ├── DeleteUsersCommandTest.php
│   │   ├── InstallModuleCommandTest.php
│   │   ├── RefreshAvatarCommandTest.php
│   │   ├── RegeneratePermissionsCommandTest.php
│   │   ├── RegenerateReferencesCommandTest.php
│   │   ├── RegenerateSearchCommandTest.php
│   │   ├── ResetMfaCommandTest.php
│   │   ├── UpdateUrlCommandTest.php
│   │   └── UpgradeDatabaseEncodingCommandTest.php
│   ├── CreatesApplication.php
│   ├── DebugViewTest.php
│   ├── Entity/
│   │   ├── BookShelfTest.php
│   │   ├── BookTest.php
│   │   ├── ChapterTest.php
│   │   ├── ConvertTest.php
│   │   ├── CopyTest.php
│   │   ├── DefaultTemplateTest.php
│   │   ├── EntityAccessTest.php
│   │   ├── EntityQueryTest.php
│   │   ├── MarkdownToHtmlTest.php
│   │   ├── PageContentFilteringTest.php
│   │   ├── PageContentTest.php
│   │   ├── PageDraftTest.php
│   │   ├── PageEditorTest.php
│   │   ├── PageRevisionTest.php
│   │   ├── PageTemplateTest.php
│   │   ├── PageTest.php
│   │   ├── SlugTest.php
│   │   └── TagTest.php
│   ├── ErrorTest.php
│   ├── Exports/
│   │   ├── ExportUiTest.php
│   │   ├── HtmlExportTest.php
│   │   ├── MarkdownExportTest.php
│   │   ├── PdfExportTest.php
│   │   ├── TextExportTest.php
│   │   ├── ZipExportTest.php
│   │   ├── ZipExportValidatorTest.php
│   │   ├── ZipImportRunnerTest.php
│   │   ├── ZipImportTest.php
│   │   ├── ZipResultData.php
│   │   └── ZipTestHelper.php
│   ├── FavouriteTest.php
│   ├── Helpers/
│   │   ├── EntityProvider.php
│   │   ├── FileProvider.php
│   │   ├── OidcJwtHelper.php
│   │   ├── PermissionsProvider.php
│   │   ├── TestServiceProvider.php
│   │   └── UserRoleProvider.php
│   ├── HomepageTest.php
│   ├── LanguageTest.php
│   ├── Meta/
│   │   ├── HelpTest.php
│   │   ├── LicensesTest.php
│   │   ├── OpenGraphTest.php
│   │   ├── OpensearchTest.php
│   │   ├── PwaManifestTest.php
│   │   └── RobotsTest.php
│   ├── Permissions/
│   │   ├── EntityOwnerChangeTest.php
│   │   ├── EntityPermissionsTest.php
│   │   ├── ExportPermissionsTest.php
│   │   ├── RolePermissionsTest.php
│   │   └── Scenarios/
│   │       ├── EntityRolePermissionsTest.php
│   │       ├── PermissionScenarioTestCase.php
│   │       └── RoleContentPermissionsTest.php
│   ├── PublicActionTest.php
│   ├── References/
│   │   ├── CrossLinkParserTest.php
│   │   └── ReferencesTest.php
│   ├── Search/
│   │   ├── EntitySearchTest.php
│   │   ├── SearchIndexingTest.php
│   │   ├── SearchOptionsTest.php
│   │   └── SiblingSearchTest.php
│   ├── SecurityHeaderTest.php
│   ├── SessionTest.php
│   ├── Settings/
│   │   ├── CustomHeadContentTest.php
│   │   ├── FooterLinksTest.php
│   │   ├── PageListLimitsTest.php
│   │   ├── RecycleBinTest.php
│   │   ├── RegenerateReferencesTest.php
│   │   ├── SettingsTest.php
│   │   └── TestEmailTest.php
│   ├── Sorting/
│   │   ├── BookSortTest.php
│   │   ├── MoveTest.php
│   │   └── SortRuleTest.php
│   ├── StatusTest.php
│   ├── TestCase.php
│   ├── Theme/
│   │   ├── LogicalThemeEventsTest.php
│   │   ├── LogicalThemeTest.php
│   │   ├── ThemeModuleTest.php
│   │   └── VisualThemeTest.php
│   ├── Unit/
│   │   ├── ConfigTest.php
│   │   ├── FrameworkAssumptionTest.php
│   │   ├── IpFormatterTest.php
│   │   ├── OidcIdTokenTest.php
│   │   ├── PageIncludeParserTest.php
│   │   └── SsrUrlValidatorTest.php
│   ├── Uploads/
│   │   ├── AttachmentTest.php
│   │   ├── AvatarTest.php
│   │   ├── DrawioTest.php
│   │   ├── ImageStorageTest.php
│   │   └── ImageTest.php
│   ├── UrlTest.php
│   ├── User/
│   │   ├── RoleManagementTest.php
│   │   ├── UserApiTokenTest.php
│   │   ├── UserManagementTest.php
│   │   ├── UserMyAccountTest.php
│   │   ├── UserPreferencesTest.php
│   │   ├── UserProfileTest.php
│   │   └── UserSearchTest.php
│   ├── Util/
│   │   └── DateFormatterTest.php
│   └── test-data/
│       ├── animated.avif
│       ├── bad-php.base64
│       ├── bad-phtml-png.base64
│       ├── bad-phtml.base64
│       └── test-file.txt
├── themes/
│   └── .gitignore
├── tsconfig.json
└── version
Download .txt
Showing preview only (824K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (8480 symbols across 1024 files)

FILE: app/Access/Controllers/ConfirmEmailController.php
  class ConfirmEmailController (line 15) | class ConfirmEmailController extends Controller
    method __construct (line 17) | public function __construct(
    method show (line 28) | public function show()
    method showAwaiting (line 37) | public function showAwaiting()
    method showAcceptForm (line 51) | public function showAcceptForm(string $token)
    method confirm (line 62) | public function confirm(Request $request)
    method resend (line 97) | public function resend()

FILE: app/Access/Controllers/ForgotPasswordController.php
  class ForgotPasswordController (line 11) | class ForgotPasswordController extends Controller
    method __construct (line 13) | public function __construct()
    method showLinkRequestForm (line 22) | public function showLinkRequestForm()
    method sendResetLinkEmail (line 30) | public function sendResetLinkEmail(Request $request)

FILE: app/Access/Controllers/HandlesPartialLogins.php
  type HandlesPartialLogins (line 9) | trait HandlesPartialLogins
    method currentOrLastAttemptedUser (line 14) | protected function currentOrLastAttemptedUser(): User

FILE: app/Access/Controllers/LoginController.php
  class LoginController (line 15) | class LoginController extends Controller
    method __construct (line 19) | public function __construct(
    method getLogin (line 31) | public function getLogin(Request $request)
    method login (line 62) | public function login(Request $request)
    method logout (line 96) | public function logout()
    method username (line 104) | protected function username(): string
    method credentials (line 112) | protected function credentials(Request $request): array
    method sendLoginResponse (line 121) | protected function sendLoginResponse(Request $request)
    method attemptLogin (line 132) | protected function attemptLogin(Request $request): bool
    method validateLogin (line 146) | protected function validateLogin(Request $request): void
    method sendLoginAttemptExceptionResponse (line 166) | protected function sendLoginAttemptExceptionResponse(LoginAttemptExcep...
    method updateIntendedFromPrevious (line 185) | protected function updateIntendedFromPrevious(): void

FILE: app/Access/Controllers/MfaBackupCodesController.php
  class MfaBackupCodesController (line 16) | class MfaBackupCodesController extends Controller
    method generate (line 25) | public function generate(BackupCodeService $codeService)
    method confirm (line 45) | public function confirm()
    method verify (line 71) | public function verify(Request $request, BackupCodeService $codeServic...

FILE: app/Access/Controllers/MfaController.php
  class MfaController (line 10) | class MfaController extends Controller
    method setup (line 17) | public function setup()
    method remove (line 36) | public function remove(string $method)
    method verify (line 52) | public function verify(Request $request)

FILE: app/Access/Controllers/MfaTotpController.php
  class MfaTotpController (line 16) | class MfaTotpController extends Controller
    method __construct (line 22) | public function __construct(
    method generate (line 30) | public function generate()
    method confirm (line 57) | public function confirm(Request $request)
    method verify (line 86) | public function verify(Request $request, LoginService $loginService, M...

FILE: app/Access/Controllers/OidcController.php
  class OidcController (line 10) | class OidcController extends Controller
    method __construct (line 12) | public function __construct(
    method login (line 21) | public function login()
    method callback (line 40) | public function callback(Request $request)
    method logout (line 71) | public function logout()

FILE: app/Access/Controllers/RegisterController.php
  class RegisterController (line 16) | class RegisterController extends Controller
    method __construct (line 18) | public function __construct(
    method getRegister (line 32) | public function getRegister()
    method postRegister (line 48) | public function postRegister(Request $request)
    method validator (line 73) | protected function validator(array $data): ValidatorContract

FILE: app/Access/Controllers/ResetPasswordController.php
  class ResetPasswordController (line 16) | class ResetPasswordController extends Controller
    method __construct (line 18) | public function __construct(
    method showResetForm (line 29) | public function showResetForm(Request $request)
    method reset (line 41) | public function reset(Request $request)
    method sendResetResponse (line 72) | protected function sendResetResponse(): RedirectResponse
    method sendResetFailedResponse (line 83) | protected function sendResetFailedResponse(Request $request, string $r...

FILE: app/Access/Controllers/Saml2Controller.php
  class Saml2Controller (line 10) | class Saml2Controller extends Controller
    method __construct (line 12) | public function __construct(
    method login (line 21) | public function login()
    method logout (line 32) | public function logout()
    method metadata (line 51) | public function metadata()
    method sls (line 64) | public function sls()
    method startAcs (line 79) | public function startAcs(Request $request)
    method processAcs (line 101) | public function processAcs(Request $request)

FILE: app/Access/Controllers/SocialController.php
  class SocialController (line 17) | class SocialController extends Controller
    method __construct (line 19) | public function __construct(
    method login (line 32) | public function login(string $socialDriver)
    method register (line 45) | public function register(string $socialDriver)
    method callback (line 60) | public function callback(Request $request, string $socialDriver)
    method detach (line 100) | public function detach(string $socialDriver)
    method socialRegisterCallback (line 113) | protected function socialRegisterCallback(string $socialDriver, Social...

FILE: app/Access/Controllers/ThrottlesLogins.php
  type ThrottlesLogins (line 11) | trait ThrottlesLogins
    method hasTooManyLoginAttempts (line 16) | protected function hasTooManyLoginAttempts(Request $request): bool
    method incrementLoginAttempts (line 27) | protected function incrementLoginAttempts(Request $request): void
    method sendLockoutResponse (line 39) | protected function sendLockoutResponse(Request $request): \Symfony\Com...
    method clearLoginAttempts (line 56) | protected function clearLoginAttempts(Request $request): void
    method throttleKey (line 64) | protected function throttleKey(Request $request): string
    method limiter (line 72) | protected function limiter(): RateLimiter
    method maxAttempts (line 80) | public function maxAttempts(): int
    method decayMinutes (line 88) | public function decayMinutes(): int

FILE: app/Access/Controllers/UserInviteController.php
  class UserInviteController (line 17) | class UserInviteController extends Controller
    method __construct (line 25) | public function __construct(UserInviteService $inviteService, UserRepo...
    method showSetPassword (line 39) | public function showSetPassword(string $token)
    method setPassword (line 57) | public function setPassword(Request $request, string $token)
    method handleTokenException (line 87) | protected function handleTokenException(Exception $exception)

FILE: app/Access/EmailConfirmationService.php
  class EmailConfirmationService (line 9) | class EmailConfirmationService extends UserTokenService
    method sendConfirmation (line 20) | public function sendConfirmation(User $user): void
    method confirmationRequired (line 35) | public function confirmationRequired(): bool

FILE: app/Access/ExternalBaseUserProvider.php
  class ExternalBaseUserProvider (line 9) | class ExternalBaseUserProvider implements UserProvider
    method retrieveById (line 14) | public function retrieveById(mixed $identifier): ?Authenticatable
    method retrieveByToken (line 24) | public function retrieveByToken(mixed $identifier, $token): null
    method updateRememberToken (line 37) | public function updateRememberToken(Authenticatable $user, $token)
    method retrieveByCredentials (line 45) | public function retrieveByCredentials(array $credentials): ?Authentica...
    method validateCredentials (line 55) | public function validateCredentials(Authenticatable $user, array $cred...
    method rehashPasswordIfRequired (line 61) | public function rehashPasswordIfRequired(Authenticatable $user, #[\Sen...

FILE: app/Access/GroupSyncService.php
  class GroupSyncService (line 9) | class GroupSyncService
    method roleMatchesGroupNames (line 15) | protected function roleMatchesGroupNames(Role $role, array $groupNames...
    method externalIdMatchesGroupNames (line 29) | protected function externalIdMatchesGroupNames(string $externalId, arr...
    method parseRoleExternalAuthId (line 40) | protected function parseRoleExternalAuthId(string $externalId): array
    method matchGroupsToSystemsRoles (line 56) | protected function matchGroupsToSystemsRoles(array $groupNames): Colle...
    method syncUserWithFoundGroups (line 73) | public function syncUserWithFoundGroups(User $user, array $userGroups,...

FILE: app/Access/Guards/AsyncExternalBaseSessionGuard.php
  class AsyncExternalBaseSessionGuard (line 12) | class AsyncExternalBaseSessionGuard extends ExternalBaseSessionGuard
    method validate (line 17) | public function validate(array $credentials = []): bool
    method attempt (line 27) | public function attempt(array $credentials = [], $remember = false): bool

FILE: app/Access/Guards/ExternalBaseSessionGuard.php
  class ExternalBaseSessionGuard (line 19) | class ExternalBaseSessionGuard implements StatefulGuard
    method __construct (line 53) | public function __construct(string $name, UserProvider $provider, Sess...
    method user (line 64) | public function user(): Authenticatable|null
    method id (line 91) | public function id(): int|null
    method once (line 105) | public function once(array $credentials = []): bool
    method onceUsingId (line 119) | public function onceUsingId($id): Authenticatable|false
    method validate (line 133) | public function validate(array $credentials = []): bool
    method attempt (line 142) | public function attempt(array $credentials = [], $remember = false): bool
    method loginUsingId (line 151) | public function loginUsingId(mixed $id, $remember = false): Authentica...
    method login (line 163) | public function login(Authenticatable $user, $remember = false): void
    method updateSession (line 173) | protected function updateSession(string|int $id): void
    method logout (line 183) | public function logout(): void
    method clearUserDataFromStorage (line 198) | protected function clearUserDataFromStorage(): void
    method getLastAttempted (line 206) | public function getLastAttempted(): Authenticatable
    method getName (line 214) | public function getName(): string
    method viaRemember (line 222) | public function viaRemember(): bool
    method getUser (line 230) | public function getUser(): Authenticatable|null
    method setUser (line 238) | public function setUser(Authenticatable $user): self

FILE: app/Access/Guards/LdapSessionGuard.php
  class LdapSessionGuard (line 17) | class LdapSessionGuard extends ExternalBaseSessionGuard
    method __construct (line 24) | public function __construct(
    method validate (line 40) | public function validate(array $credentials = []): bool
    method attempt (line 62) | public function attempt(array $credentials = [], $remember = false): bool
    method createNewFromLdapAndCreds (line 108) | protected function createNewFromLdapAndCreds(array $ldapUserDetails, a...

FILE: app/Access/Ldap.php
  class Ldap (line 10) | class Ldap
    method connect (line 17) | public function connect(string $hostName)
    method setOption (line 27) | public function setOption($ldapConnection, int $option, mixed $value):...
    method startTls (line 35) | public function startTls($ldapConnection): bool
    method setVersion (line 45) | public function setVersion($ldapConnection, int $version): bool
    method search (line 57) | public function search($ldapConnection, string $baseDn, string $filter...
    method read (line 69) | public function read($ldapConnection, string $baseDn, string $filter, ...
    method getEntries (line 80) | public function getEntries($ldapConnection, $ldapSearchResult): array|...
    method searchAndGetEntries (line 90) | public function searchAndGetEntries($ldapConnection, string $baseDn, s...
    method bind (line 102) | public function bind($ldapConnection, ?string $bindRdn = null, ?string...
    method explodeDn (line 110) | public function explodeDn(string $dn, int $withAttrib): array|false
    method escape (line 118) | public function escape(string $value, string $ignore = '', int $flags ...

FILE: app/Access/LdapService.php
  class LdapService (line 16) | class LdapService
    method __construct (line 26) | public function __construct(
    method shouldSyncGroups (line 38) | public function shouldSyncGroups(): bool
    method getUserWithAttributes (line 48) | private function getUserWithAttributes(string $userName, array $attrib...
    method getUserDisplayName (line 77) | protected function getUserDisplayName(array $userDetails, array $displ...
    method getUserDetails (line 100) | public function getUserDetails(string $userName): ?array
    method getUserResponseProperty (line 144) | protected function getUserResponseProperty(array $userDetails, string ...
    method validateUserCredentials (line 169) | public function validateUserCredentials(?array $ldapUserDetails, strin...
    method bindSystemUser (line 194) | protected function bindSystemUser($connection): void
    method getConnection (line 219) | protected function getConnection()
    method configureTlsCaCerts (line 284) | protected function configureTlsCaCerts(string $caCertPath): void
    method parseServerString (line 305) | protected function parseServerString(string $serverString): string
    method buildFilter (line 319) | protected function buildFilter(string $filterString, array $attrs): st...
    method getUserGroups (line 339) | public function getUserGroups(string $userName): array
    method extractGroupNamesFromLdapGroupDns (line 364) | protected function extractGroupNamesFromLdapGroupDns(array $groupDNs):...
    method getGroupsRecursive (line 384) | protected function getGroupsRecursive(array $groupDNs, array $checked)...
    method getParentsOfGroup (line 409) | protected function getParentsOfGroup(string $groupDN): array
    method extractGroupsFromSearchResponseEntry (line 429) | protected function extractGroupsFromSearchResponseEntry(array $ldapEnt...
    method syncGroups (line 455) | public function syncGroups(User $user, string $username): void
    method saveAndAttachAvatar (line 465) | public function saveAndAttachAvatar(User $user, array $ldapUserDetails...

FILE: app/Access/LoginService.php
  class LoginService (line 17) | class LoginService
    method __construct (line 21) | public function __construct(
    method login (line 36) | public function login(User $user, string $method, bool $remember = fal...
    method reattemptLoginFor (line 67) | public function reattemptLoginFor(User $user): void
    method getLastLoginAttemptUser (line 82) | public function getLastLoginAttemptUser(): ?User
    method getLastLoginAttemptDetails (line 95) | protected function getLastLoginAttemptDetails(): array
    method setLastLoginAttemptedForUser (line 118) | protected function setLastLoginAttemptedForUser(User $user, string $me...
    method clearLastLoginAttempted (line 129) | protected function clearLastLoginAttempted(): void
    method needsMfaVerification (line 137) | public function needsMfaVerification(User $user): bool
    method awaitingEmailConfirmation (line 145) | public function awaitingEmailConfirmation(User $user): bool
    method attempt (line 159) | public function attempt(array $credentials, string $method, bool $reme...
    method areCredentialsForGuest (line 184) | protected function areCredentialsForGuest(array $credentials): bool
    method logout (line 199) | public function logout(): string
    method shouldAutoInitiate (line 211) | public function shouldAutoInitiate(): bool

FILE: app/Access/Mfa/BackupCodeService.php
  class BackupCodeService (line 7) | class BackupCodeService
    method generateNewSet (line 12) | public function generateNewSet(): array
    method inputCodeExistsInSet (line 28) | public function inputCodeExistsInSet(string $code, string $codeSet): bool
    method removeInputCodeFromSet (line 40) | public function removeInputCodeFromSet(string $code, string $codeSet):...
    method countCodesInSet (line 53) | public function countCodesInSet(string $codeSet): int
    method cleanInputCode (line 58) | protected function cleanInputCode(string $code): string

FILE: app/Access/Mfa/MfaSession.php
  class MfaSession (line 7) | class MfaSession
    method isRequiredForUser (line 12) | public function isRequiredForUser(User $user): bool
    method isPendingMfaSetup (line 21) | public function isPendingMfaSetup(User $user): bool
    method userRoleEnforcesMfa (line 29) | protected function userRoleEnforcesMfa(User $user): bool
    method isVerifiedForUser (line 39) | public function isVerifiedForUser(User $user): bool
    method markVerifiedForUser (line 47) | public function markVerifiedForUser(User $user): void
    method getMfaVerifiedSessionKey (line 55) | protected function getMfaVerifiedSessionKey(User $user): string

FILE: app/Access/Mfa/MfaValue.php
  class MfaValue (line 18) | class MfaValue extends Model
    method allMethods (line 30) | public static function allMethods(): array
    method upsertWithValue (line 39) | public static function upsertWithValue(User $user, string $method, str...
    method getValueForUser (line 53) | public static function getValueForUser(User $user, string $method): ?s...
    method getValue (line 67) | protected function getValue(): string
    method setValue (line 75) | protected function setValue($value): void

FILE: app/Access/Mfa/TotpService.php
  class TotpService (line 15) | class TotpService
    method __construct (line 17) | public function __construct(
    method generateSecret (line 30) | public function generateSecret(): string
    method generateUrl (line 39) | public function generateUrl(string $secret, User $user): string
    method generateQrCodeSvg (line 51) | public function generateQrCodeSvg(string $url): string
    method verifyCode (line 67) | public function verifyCode(string $code, string $secret): bool

FILE: app/Access/Mfa/TotpValidationRule.php
  class TotpValidationRule (line 8) | class TotpValidationRule implements ValidationRule
    method __construct (line 14) | public function __construct(
    method validate (line 20) | public function validate(string $attribute, mixed $value, Closure $fai...

FILE: app/Access/Notifications/ConfirmEmailNotification.php
  class ConfirmEmailNotification (line 9) | class ConfirmEmailNotification extends MailNotification
    method __construct (line 11) | public function __construct(
    method toMail (line 16) | public function toMail(User $notifiable): MailMessage

FILE: app/Access/Notifications/ResetPasswordNotification.php
  class ResetPasswordNotification (line 9) | class ResetPasswordNotification extends MailNotification
    method __construct (line 11) | public function __construct(
    method toMail (line 16) | public function toMail(User $notifiable): MailMessage

FILE: app/Access/Notifications/UserInviteNotification.php
  class UserInviteNotification (line 9) | class UserInviteNotification extends MailNotification
    method __construct (line 11) | public function __construct(
    method toMail (line 16) | public function toMail(User $notifiable): MailMessage

FILE: app/Access/Oidc/OidcAccessToken.php
  class OidcAccessToken (line 8) | class OidcAccessToken extends AccessToken
    method __construct (line 18) | public function __construct(array $options = [])
    method validate (line 28) | private function validate(array $options): void
    method getIdToken (line 49) | public function getIdToken(): string

FILE: app/Access/Oidc/OidcException.php
  class OidcException (line 7) | class OidcException extends Exception

FILE: app/Access/Oidc/OidcIdToken.php
  class OidcIdToken (line 5) | class OidcIdToken extends OidcJwtWithClaims implements ProvidesClaims
    method validate (line 12) | public function validate(string $clientId): bool
    method validateTokenClaims (line 26) | protected function validateTokenClaims(string $clientId): void

FILE: app/Access/Oidc/OidcInvalidKeyException.php
  class OidcInvalidKeyException (line 5) | class OidcInvalidKeyException extends \Exception

FILE: app/Access/Oidc/OidcInvalidTokenException.php
  class OidcInvalidTokenException (line 7) | class OidcInvalidTokenException extends Exception

FILE: app/Access/Oidc/OidcIssuerDiscoveryException.php
  class OidcIssuerDiscoveryException (line 7) | class OidcIssuerDiscoveryException extends Exception

FILE: app/Access/Oidc/OidcJwtSigningKey.php
  class OidcJwtSigningKey (line 10) | class OidcJwtSigningKey
    method __construct (line 27) | public function __construct($jwkOrKeyPath)
    method loadFromPath (line 41) | protected function loadFromPath(string $path)
    method loadFromJwkArray (line 61) | protected function loadFromJwkArray(array $jwk)
    method verify (line 106) | public function verify(string $content, string $signature): bool
    method toPem (line 114) | public function toPem(): string

FILE: app/Access/Oidc/OidcJwtWithClaims.php
  class OidcJwtWithClaims (line 5) | class OidcJwtWithClaims implements ProvidesClaims
    method __construct (line 18) | public function __construct(string $token, string $issuer, array $keys)
    method parse (line 28) | protected function parse(string $token): void
    method parseEncodedTokenPart (line 40) | protected function parseEncodedTokenPart(string $part): array
    method base64UrlDecode (line 52) | protected function base64UrlDecode(string $encoded): string
    method validateCommonTokenDetails (line 62) | public function validateCommonTokenDetails(string $clientId): bool
    method getClaim (line 75) | public function getClaim(string $claim): mixed
    method getAllClaims (line 83) | public function getAllClaims(): array
    method replaceClaims (line 91) | public function replaceClaims(array $claims): void
    method validateTokenStructure (line 102) | protected function validateTokenStructure(): void
    method validateTokenSignature (line 120) | protected function validateTokenSignature(): void
    method validateCommonClaims (line 154) | protected function validateCommonClaims(string $clientId): void

FILE: app/Access/Oidc/OidcOAuthProvider.php
  class OidcOAuthProvider (line 19) | class OidcOAuthProvider extends AbstractProvider
    method getBaseAuthorizationUrl (line 34) | public function getBaseAuthorizationUrl(): string
    method getBaseAccessTokenUrl (line 42) | public function getBaseAccessTokenUrl(array $params): string
    method getResourceOwnerDetailsUrl (line 50) | public function getResourceOwnerDetailsUrl(AccessToken $token): string
    method addScope (line 58) | public function addScope(string $scope): void
    method getDefaultScopes (line 70) | protected function getDefaultScopes(): array
    method getScopeSeparator (line 79) | protected function getScopeSeparator(): string
    method checkResponse (line 88) | protected function checkResponse(ResponseInterface $response, $data): ...
    method createResourceOwner (line 103) | protected function createResourceOwner(array $response, AccessToken $t...
    method createAccessToken (line 114) | protected function createAccessToken(array $response, AbstractGrant $g...
    method getPkceMethod (line 123) | protected function getPkceMethod(): string

FILE: app/Access/Oidc/OidcProviderSettings.php
  class OidcProviderSettings (line 16) | class OidcProviderSettings
    method __construct (line 31) | public function __construct(array $settings)
    method applySettingsFromArray (line 40) | protected function applySettingsFromArray(array $settingsArray): void
    method validateInitial (line 54) | protected function validateInitial(): void
    method validate (line 73) | public function validate(): void
    method discoverFromIssuer (line 97) | public function discoverFromIssuer(ClientInterface $httpClient, Reposi...
    method loadSettingsFromIssuerDiscovery (line 114) | protected function loadSettingsFromIssuerDiscovery(ClientInterface $ht...
    method filterKeys (line 158) | protected function filterKeys(array $keys): array
    method loadKeysFromUri (line 174) | protected function loadKeysFromUri(string $uri, ClientInterface $httpC...
    method arrayForOAuthProvider (line 190) | public function arrayForOAuthProvider(): array

FILE: app/Access/Oidc/OidcService.php
  class OidcService (line 24) | class OidcService
    method __construct (line 26) | public function __construct(
    method login (line 44) | public function login(): array
    method processAuthorizeResponse (line 73) | public function processAuthorizeResponse(?string $authorizationCode): ...
    method getProviderSettings (line 93) | protected function getProviderSettings(): OidcProviderSettings
    method getProvider (line 136) | protected function getProvider(OidcProviderSettings $settings): OidcOA...
    method getAdditionalScopes (line 158) | protected function getAdditionalScopes(): array
    method processAccessTokenCallback (line 176) | protected function processAccessTokenCallback(OidcAccessToken $accessT...
    method getUserDetailsFromToken (line 247) | protected function getUserDetailsFromToken(OidcIdToken $idToken, OidcA...
    method config (line 286) | protected function config(): array
    method shouldSyncGroups (line 294) | protected function shouldSyncGroups(): bool
    method logout (line 305) | public function logout(): string

FILE: app/Access/Oidc/OidcUserDetails.php
  class OidcUserDetails (line 7) | class OidcUserDetails
    method __construct (line 9) | public function __construct(
    method isFullyPopulated (line 21) | public function isFullyPopulated(bool $groupSyncActive): bool
    method populate (line 34) | public function populate(
    method getUserDisplayName (line 47) | protected static function getUserDisplayName(string $displayNameClaims...
    method getUserGroups (line 62) | protected static function getUserGroups(string $groupsClaim, ProvidesC...
    method getPicture (line 78) | protected static function getPicture(ProvidesClaims $claims): ?string

FILE: app/Access/Oidc/OidcUserinfoResponse.php
  class OidcUserinfoResponse (line 7) | class OidcUserinfoResponse implements ProvidesClaims
    method __construct (line 12) | public function __construct(ResponseInterface $response, string $issue...
    method validate (line 30) | public function validate(string $idTokenSub, string $clientId): bool
    method getClaim (line 60) | public function getClaim(string $claim): mixed
    method getAllClaims (line 65) | public function getAllClaims(): array

FILE: app/Access/Oidc/ProvidesClaims.php
  type ProvidesClaims (line 5) | interface ProvidesClaims
    method getClaim (line 11) | public function getClaim(string $claim): mixed;
    method getAllClaims (line 16) | public function getAllClaims(): array;

FILE: app/Access/RegistrationService.php
  class RegistrationService (line 15) | class RegistrationService
    method __construct (line 17) | public function __construct(
    method ensureRegistrationAllowed (line 28) | public function ensureRegistrationAllowed()
    method registrationAllowed (line 39) | protected function registrationAllowed(): bool
    method findOrRegister (line 53) | public function findOrRegister(string $name, string $email, string $ex...
    method registerUser (line 78) | public function registerUser(array $userData, ?SocialAccount $socialAc...
    method ensureEmailDomainAllowed (line 133) | protected function ensureEmailDomainAllowed(string $userEmail): void

FILE: app/Access/Saml2Service.php
  class Saml2Service (line 21) | class Saml2Service
    method __construct (line 25) | public function __construct(
    method login (line 38) | public function login(): array
    method logout (line 56) | public function logout(User $user): array
    method processAcsResponse (line 95) | public function processAcsResponse(?string $requestId, string $samlRes...
    method processSlsResponse (line 127) | public function processSlsResponse(?string $requestId): string
    method metadata (line 156) | public function metadata(): string
    method getToolkit (line 179) | protected function getToolkit(bool $spOnly = false): Auth
    method loadOneloginServiceProviderDetails (line 202) | protected function loadOneloginServiceProviderDetails(): array
    method shouldSyncGroups (line 223) | protected function shouldSyncGroups(): bool
    method getUserDisplayName (line 231) | protected function getUserDisplayName(array $samlAttributes, string $d...
    method getExternalId (line 256) | protected function getExternalId(array $samlAttributes, string $defaul...
    method getUserDetails (line 271) | protected function getUserDetails(string $samlID, $samlAttributes): array
    method getUserGroups (line 290) | public function getUserGroups(array $samlAttributes): array
    method simplifyValue (line 307) | protected function simplifyValue(array $data, $defaultValue)
    method getSamlResponseAttribute (line 325) | protected function getSamlResponseAttribute(array $samlAttributes, str...
    method processLoginCallback (line 343) | public function processLoginCallback(string $samlID, array $samlAttrib...

FILE: app/Access/SocialAccount.php
  class SocialAccount (line 15) | class SocialAccount extends Model implements Loggable
    method user (line 24) | public function user(): BelongsTo
    method logDescriptor (line 32) | public function logDescriptor(): string

FILE: app/Access/SocialAuthService.php
  class SocialAuthService (line 16) | class SocialAuthService
    method __construct (line 18) | public function __construct(
    method startLogIn (line 30) | public function startLogIn(string $socialDriver): RedirectResponse
    method startRegister (line 43) | public function startRegister(string $socialDriver): RedirectResponse
    method handleRegistrationCallback (line 56) | public function handleRegistrationCallback(string $socialDriver, Socia...
    method getSocialUser (line 77) | public function getSocialUser(string $socialDriver): SocialUser
    method handleLoginCallback (line 90) | public function handleLoginCallback(string $socialDriver, SocialUser $...
    method drivers (line 145) | public function drivers(): SocialDriverManager
    method newSocialAccount (line 153) | public function newSocialAccount(string $socialDriver, SocialUser $soc...
    method detachSocialAccount (line 165) | public function detachSocialAccount(string $socialDriver): void
    method getDriverForRedirect (line 173) | protected function getDriverForRedirect(string $driverName): Provider

FILE: app/Access/SocialDriverManager.php
  class SocialDriverManager (line 10) | class SocialDriverManager
    method isAutoRegisterEnabled (line 43) | public function isAutoRegisterEnabled(string $driver): bool
    method isAutoConfirmEmailEnabled (line 51) | public function isAutoConfirmEmailEnabled(string $driver): bool
    method getActive (line 60) | public function getActive(): array
    method getConfigureForRedirectCallback (line 79) | public function getConfigureForRedirectCallback(string $driver): callable
    method addSocialDriver (line 91) | public function addSocialDriver(
    method getName (line 110) | protected function getName(string $driver): string
    method getDriverConfigProperty (line 115) | protected function getDriverConfigProperty(string $driver, string $pro...
    method ensureDriverActive (line 125) | public function ensureDriverActive(string $driverName): void
    method checkDriverConfigured (line 139) | protected function checkDriverConfigured(string $driver): bool

FILE: app/Access/UserInviteException.php
  class UserInviteException (line 7) | class UserInviteException extends Exception

FILE: app/Access/UserInviteService.php
  class UserInviteService (line 8) | class UserInviteService extends UserTokenService
    method sendInvitation (line 18) | public function sendInvitation(User $user)

FILE: app/Access/UserTokenService.php
  class UserTokenService (line 13) | class UserTokenService
    method deleteByUser (line 28) | public function deleteByUser(User $user): void
    method checkTokenAndGetUserId (line 41) | public function checkTokenAndGetUserId(string $token): int
    method generateToken (line 59) | protected function generateToken(): string
    method createTokenForUser (line 72) | protected function createTokenForUser(User $user): string
    method tokenExists (line 88) | protected function tokenExists(string $token): bool
    method getEntryByToken (line 97) | protected function getEntryByToken(string $token): ?stdClass
    method entryExpired (line 107) | protected function entryExpired(stdClass $tokenEntry): bool

FILE: app/Activity/ActivityQueries.php
  class ActivityQueries (line 17) | class ActivityQueries
    method __construct (line 19) | public function __construct(
    method latest (line 28) | public function latest(int $count = 20, int $page = 0): array
    method entityActivity (line 47) | public function entityActivity(Entity $entity, int $count = 20, int $p...
    method userActivity (line 84) | public function userActivity(User $user, int $count = 20, int $page = ...
    method filterSimilar (line 102) | protected function filterSimilar(iterable $activities): array

FILE: app/Activity/ActivityType.php
  class ActivityType (line 5) | class ActivityType
    method all (line 81) | public static function all(): array

FILE: app/Activity/CommentRepo.php
  class CommentRepo (line 13) | class CommentRepo
    method getById (line 18) | public function getById(int $id): Comment
    method getVisibleById (line 27) | public function getVisibleById(int $id): Comment
    method getQueryForVisible (line 36) | public function getQueryForVisible(): Builder
    method create (line 44) | public function create(Entity $entity, string $html, ?int $parentId, s...
    method update (line 84) | public function update(Comment $comment, string $html): Comment
    method archive (line 99) | public function archive(Comment $comment, bool $log = true): Comment
    method unarchive (line 118) | public function unarchive(Comment $comment, bool $log = true): Comment
    method delete (line 137) | public function delete(Comment $comment): void
    method getNextLocalId (line 147) | protected function getNextLocalId(Entity $entity): int

FILE: app/Activity/Controllers/AuditLogApiController.php
  class AuditLogApiController (line 9) | class AuditLogApiController extends ApiController
    method list (line 18) | public function list()

FILE: app/Activity/Controllers/AuditLogController.php
  class AuditLogController (line 13) | class AuditLogController extends Controller
    method index (line 15) | public function index(Request $request)

FILE: app/Activity/Controllers/CommentApiController.php
  class CommentApiController (line 25) | class CommentApiController extends ApiController
    method __construct (line 40) | public function __construct(
    method list (line 49) | public function list(): JsonResponse
    method create (line 63) | public function create(Request $request): JsonResponse
    method read (line 83) | public function read(string $id): JsonResponse
    method update (line 113) | public function update(Request $request, string $id): JsonResponse
    method delete (line 139) | public function delete(string $id): Response

FILE: app/Activity/Controllers/CommentController.php
  class CommentController (line 14) | class CommentController extends Controller
    method __construct (line 16) | public function __construct(
    method savePageComment (line 27) | public function savePageComment(Request $request, int $pageId)
    method update (line 56) | public function update(Request $request, int $commentId)
    method archive (line 77) | public function archive(int $id)
    method unarchive (line 97) | public function unarchive(int $id)
    method destroy (line 117) | public function destroy(int $id)

FILE: app/Activity/Controllers/FavouriteController.php
  class FavouriteController (line 10) | class FavouriteController extends Controller
    method __construct (line 12) | public function __construct(
    method index (line 20) | public function index(Request $request, QueryTopFavourites $topFavouri...
    method add (line 40) | public function add(Request $request)
    method remove (line 58) | public function remove(Request $request)

FILE: app/Activity/Controllers/TagController.php
  class TagController (line 10) | class TagController extends Controller
    method __construct (line 12) | public function __construct(
    method index (line 20) | public function index(Request $request)
    method getNameSuggestions (line 47) | public function getNameSuggestions(Request $request)
    method getValueSuggestions (line 58) | public function getValueSuggestions(Request $request)

FILE: app/Activity/Controllers/WatchController.php
  class WatchController (line 11) | class WatchController extends Controller
    method update (line 13) | public function update(Request $request, MixedEntityRequestHelper $ent...

FILE: app/Activity/Controllers/WebhookController.php
  class WebhookController (line 13) | class WebhookController extends Controller
    method __construct (line 15) | public function __construct()
    method index (line 25) | public function index(Request $request)
    method create (line 49) | public function create()
    method store (line 59) | public function store(Request $request)
    method edit (line 82) | public function edit(string $id)
    method update (line 97) | public function update(Request $request, string $id)
    method delete (line 122) | public function delete(string $id)
    method destroy (line 135) | public function destroy(string $id)

FILE: app/Activity/DispatchWebhookJob.php
  class DispatchWebhookJob (line 20) | class DispatchWebhookJob implements ShouldQueue
    method __construct (line 37) | public function __construct(Webhook $webhook, string $event, Loggable|...
    method handle (line 52) | public function handle(HttpRequestService $http)

FILE: app/Activity/Models/Activity.php
  class Activity (line 26) | class Activity extends Model
    method loggable (line 36) | public function loggable(): MorphTo
    method user (line 44) | public function user(): BelongsTo
    method jointPermissions (line 49) | public function jointPermissions(): HasMany
    method getText (line 58) | public function getText(): string
    method isForEntity (line 66) | public function isForEntity(): bool
    method isSimilarTo (line 76) | public function isSimilarTo(self $activityB): bool

FILE: app/Activity/Models/Comment.php
  class Comment (line 28) | class Comment extends Model implements Loggable, OwnableInterface
    method entity (line 43) | public function entity(): MorphTo
    method parent (line 64) | public function parent(): BelongsTo
    method isUpdated (line 74) | public function isUpdated(): bool
    method logDescriptor (line 79) | public function logDescriptor(): string
    method safeHtml (line 84) | public function safeHtml(): string
    method jointPermissions (line 90) | public function jointPermissions(): HasMany
    method scopeVisible (line 100) | public function scopeVisible(Builder $query): Builder

FILE: app/Activity/Models/Favouritable.php
  type Favouritable (line 7) | interface Favouritable
    method favourites (line 12) | public function favourites(): MorphMany;

FILE: app/Activity/Models/Favourite.php
  class Favourite (line 11) | class Favourite extends Model
    method favouritable (line 20) | public function favouritable(): MorphTo
    method jointPermissions (line 25) | public function jointPermissions(): HasMany

FILE: app/Activity/Models/Loggable.php
  type Loggable (line 5) | interface Loggable
    method logDescriptor (line 10) | public function logDescriptor(): string;

FILE: app/Activity/Models/MentionHistory.php
  class MentionHistory (line 17) | class MentionHistory extends Model

FILE: app/Activity/Models/Tag.php
  class Tag (line 19) | class Tag extends Model
    method entity (line 29) | public function entity(): MorphTo
    method jointPermissions (line 34) | public function jointPermissions(): HasMany
    method nameUrl (line 43) | public function nameUrl(): string
    method valueUrl (line 51) | public function valueUrl(): string

FILE: app/Activity/Models/View.php
  class View (line 20) | class View extends Model
    method viewable (line 27) | public function viewable(): MorphTo
    method jointPermissions (line 32) | public function jointPermissions(): HasMany
    method incrementFor (line 41) | public static function incrementFor(Viewable $viewable): int

FILE: app/Activity/Models/Viewable.php
  type Viewable (line 7) | interface Viewable
    method views (line 12) | public function views(): MorphMany;

FILE: app/Activity/Models/Watch.php
  class Watch (line 22) | class Watch extends Model
    method watchable (line 28) | public function watchable(): MorphTo
    method jointPermissions (line 33) | public function jointPermissions(): HasMany
    method getLevelName (line 39) | public function getLevelName(): string
    method ignoring (line 44) | public function ignoring(): bool

FILE: app/Activity/Models/Webhook.php
  class Webhook (line 23) | class Webhook extends Model implements Loggable
    method trackedEvents (line 37) | public function trackedEvents(): HasMany
    method updateTrackedEvents (line 45) | public function updateTrackedEvents(array $events): void
    method tracksEvent (line 65) | public function tracksEvent(string $event): bool
    method getUrl (line 73) | public function getUrl(string $path = ''): string
    method logDescriptor (line 81) | public function logDescriptor(): string

FILE: app/Activity/Models/WebhookTrackedEvent.php
  class WebhookTrackedEvent (line 13) | class WebhookTrackedEvent extends Model

FILE: app/Activity/Notifications/Handlers/BaseNotificationHandler.php
  class BaseNotificationHandler (line 13) | abstract class BaseNotificationHandler implements NotificationHandler
    method sendNotificationToUserIds (line 19) | protected function sendNotificationToUserIds(string $notification, arr...

FILE: app/Activity/Notifications/Handlers/CommentCreationNotificationHandler.php
  class CommentCreationNotificationHandler (line 15) | class CommentCreationNotificationHandler extends BaseNotificationHandler
    method handle (line 17) | public function handle(Activity $activity, Loggable|string $detail, Us...

FILE: app/Activity/Notifications/Handlers/CommentMentionNotificationHandler.php
  class CommentMentionNotificationHandler (line 18) | class CommentMentionNotificationHandler extends BaseNotificationHandler
    method handle (line 20) | public function handle(Activity $activity, Loggable|string $detail, Us...
    method logMentions (line 58) | protected function logMentions(Collection $mentionedUsers, Comment $co...
    method getPreviouslyNotifiedUserIds (line 77) | protected function getPreviouslyNotifiedUserIds(Comment $comment): array

FILE: app/Activity/Notifications/Handlers/NotificationHandler.php
  type NotificationHandler (line 9) | interface NotificationHandler
    method handle (line 16) | public function handle(Activity $activity, string|Loggable $detail, Us...

FILE: app/Activity/Notifications/Handlers/PageCreationNotificationHandler.php
  class PageCreationNotificationHandler (line 13) | class PageCreationNotificationHandler extends BaseNotificationHandler
    method handle (line 15) | public function handle(Activity $activity, Loggable|string $detail, Us...

FILE: app/Activity/Notifications/Handlers/PageUpdateNotificationHandler.php
  class PageUpdateNotificationHandler (line 15) | class PageUpdateNotificationHandler extends BaseNotificationHandler
    method handle (line 17) | public function handle(Activity $activity, Loggable|string $detail, Us...

FILE: app/Activity/Notifications/MessageParts/EntityLinkMessageLine.php
  class EntityLinkMessageLine (line 12) | class EntityLinkMessageLine implements Htmlable, Stringable
    method __construct (line 14) | public function __construct(
    method toHtml (line 20) | public function toHtml(): string
    method __toString (line 25) | public function __toString(): string

FILE: app/Activity/Notifications/MessageParts/EntityPathMessageLine.php
  class EntityPathMessageLine (line 12) | class EntityPathMessageLine implements Htmlable, Stringable
    method __construct (line 19) | public function __construct(
    method toHtml (line 25) | public function toHtml(): string
    method __toString (line 31) | public function __toString(): string

FILE: app/Activity/Notifications/MessageParts/LinkedMailMessageLine.php
  class LinkedMailMessageLine (line 13) | class LinkedMailMessageLine implements Htmlable, Stringable
    method __construct (line 15) | public function __construct(
    method toHtml (line 22) | public function toHtml(): string
    method __toString (line 28) | public function __toString(): string

FILE: app/Activity/Notifications/MessageParts/ListMessageLine.php
  class ListMessageLine (line 12) | class ListMessageLine implements Htmlable, Stringable
    method __construct (line 14) | public function __construct(
    method toHtml (line 19) | public function toHtml(): string
    method __toString (line 28) | public function __toString(): string

FILE: app/Activity/Notifications/Messages/BaseActivityNotification.php
  class BaseActivityNotification (line 16) | abstract class BaseActivityNotification extends MailNotification
    method __construct (line 20) | public function __construct(
    method toArray (line 32) | public function toArray($notifiable)
    method buildReasonFooterLine (line 43) | protected function buildReasonFooterLine(LocaleDefinition $locale): Li...
    method buildPagePathLine (line 57) | protected function buildPagePathLine(Page $page, User $notifiable): ?E...

FILE: app/Activity/Notifications/Messages/CommentCreationNotification.php
  class CommentCreationNotification (line 12) | class CommentCreationNotification extends BaseActivityNotification
    method toMail (line 14) | public function toMail(User $notifiable): MailMessage

FILE: app/Activity/Notifications/Messages/CommentMentionNotification.php
  class CommentMentionNotification (line 12) | class CommentMentionNotification extends BaseActivityNotification
    method toMail (line 14) | public function toMail(User $notifiable): MailMessage

FILE: app/Activity/Notifications/Messages/PageCreationNotification.php
  class PageCreationNotification (line 11) | class PageCreationNotification extends BaseActivityNotification
    method toMail (line 13) | public function toMail(User $notifiable): MailMessage

FILE: app/Activity/Notifications/Messages/PageUpdateNotification.php
  class PageUpdateNotification (line 11) | class PageUpdateNotification extends BaseActivityNotification
    method toMail (line 13) | public function toMail(User $notifiable): MailMessage

FILE: app/Activity/Notifications/NotificationManager.php
  class NotificationManager (line 15) | class NotificationManager
    method handle (line 22) | public function handle(Activity $activity, string|Loggable $detail, Us...
    method registerHandler (line 36) | public function registerHandler(string $activityType, string $handlerC...
    method loadDefaultHandlers (line 47) | public function loadDefaultHandlers(): void

FILE: app/Activity/Queries/WebhooksAllPaginatedAndSorted.php
  class WebhooksAllPaginatedAndSorted (line 12) | class WebhooksAllPaginatedAndSorted
    method run (line 14) | public function run(int $count, SimpleListOptions $listOptions): Lengt...

FILE: app/Activity/TagRepo.php
  class TagRepo (line 13) | class TagRepo
    method __construct (line 15) | public function __construct(
    method queryWithTotals (line 23) | public function queryWithTotals(SimpleListOptions $listOptions, string...
    method getNameSuggestions (line 67) | public function getNameSuggestions(string $searchTerm): Collection
    method getValueSuggestions (line 89) | public function getValueSuggestions(string $searchTerm, string $tagNam...
    method saveTagsToEntity (line 114) | public function saveTagsToEntity(Entity $entity, array $tags = []): it...
    method newInstanceFromInput (line 131) | protected function newInstanceFromInput(array $input): Tag

FILE: app/Activity/Tools/ActivityLogger.php
  class ActivityLogger (line 16) | class ActivityLogger
    method __construct (line 18) | public function __construct(
    method add (line 27) | public function add(string $type, string|Loggable $detail = ''): void
    method newActivityForUser (line 50) | protected function newActivityForUser(string $type): Activity
    method removeEntity (line 64) | public function removeEntity(Entity $entity): void
    method setNotification (line 76) | protected function setNotification(string $type): void
    method dispatchWebhooks (line 85) | protected function dispatchWebhooks(string $type, string|Loggable $det...
    method logFailedLogin (line 104) | public function logFailedLogin(string $username): void

FILE: app/Activity/Tools/CommentTree.php
  class CommentTree (line 9) | class CommentTree
    method __construct (line 23) | public function __construct(
    method enabled (line 30) | public function enabled(): bool
    method empty (line 35) | public function empty(): bool
    method count (line 40) | public function count(): int
    method getActive (line 45) | public function getActive(): array
    method activeThreadCount (line 50) | public function activeThreadCount(): int
    method getArchived (line 55) | public function getArchived(): array
    method archivedThreadCount (line 60) | public function archivedThreadCount(): int
    method getCommentNodeForId (line 65) | public function getCommentNodeForId(int $commentId): ?CommentTreeNode
    method canUpdateAny (line 76) | public function canUpdateAny(): bool
    method loadVisibleHtml (line 87) | public function loadVisibleHtml(): void
    method createTree (line 99) | protected function createTree(array $comments): array
    method createTreeNodeForId (line 127) | protected function createTreeNodeForId(int $id, int $depth, array &$by...
    method loadComments (line 142) | protected function loadComments(): array

FILE: app/Activity/Tools/CommentTreeNode.php
  class CommentTreeNode (line 7) | class CommentTreeNode
    method __construct (line 17) | public function __construct(Comment $comment, int $depth, array $child...

FILE: app/Activity/Tools/EntityWatchers.php
  class EntityWatchers (line 11) | class EntityWatchers
    method __construct (line 23) | public function __construct(
    method getWatcherUserIds (line 30) | public function getWatcherUserIds(): array
    method isUserIgnoring (line 35) | public function isUserIgnoring(int $userId): bool
    method build (line 40) | protected function build(): void
    method getRelevantWatches (line 64) | protected function getRelevantWatches(): array

FILE: app/Activity/Tools/IpFormatter.php
  class IpFormatter (line 5) | class IpFormatter
    method __construct (line 10) | public function __construct(string $ip, int $precision)
    method format (line 16) | public function format(): string
    method maskIpv4 (line 25) | protected function maskIpv4(): string
    method maskIpv6 (line 37) | protected function maskIpv6(): string
    method isIpv6 (line 49) | protected function isIpv6(): bool
    method explodeAndExpandIp (line 54) | protected function explodeAndExpandIp(string $separator, int $targetLe...
    method fromCurrentRequest (line 71) | public static function fromCurrentRequest(): self

FILE: app/Activity/Tools/MentionParser.php
  class MentionParser (line 8) | class MentionParser
    method parseUserIdsFromHtml (line 10) | public function parseUserIdsFromHtml(string $html): array

FILE: app/Activity/Tools/TagClassGenerator.php
  class TagClassGenerator (line 11) | class TagClassGenerator
    method __construct (line 13) | public function __construct(
    method generate (line 21) | public function generate(): array
    method generateAsString (line 47) | public function generateAsString(): string
    method generateClassesForTag (line 55) | protected function generateClassesForTag(Tag $tag, string $prefix = ''...
    method normalizeTagClassString (line 68) | protected function normalizeTagClassString(string $value): string

FILE: app/Activity/Tools/UserEntityWatchOptions.php
  class UserEntityWatchOptions (line 14) | class UserEntityWatchOptions
    method __construct (line 18) | public function __construct(
    method canWatch (line 24) | public function canWatch(): bool
    method getWatchLevel (line 29) | public function getWatchLevel(): string
    method isWatching (line 34) | public function isWatching(): bool
    method getWatchedParent (line 39) | public function getWatchedParent(): ?WatchedParentDetails
    method updateLevelByName (line 55) | public function updateLevelByName(string $level): void
    method updateLevelByValue (line 61) | public function updateLevelByValue(int $level): void
    method getWatchMap (line 71) | public function getWatchMap(): array
    method getWatchLevelValue (line 103) | protected function getWatchLevelValue()
    method updateLevel (line 108) | protected function updateLevel(int $levelValue): void
    method remove (line 120) | protected function remove(): void
    method entityQuery (line 126) | protected function entityQuery(): Builder

FILE: app/Activity/Tools/WatchedParentDetails.php
  class WatchedParentDetails (line 7) | class WatchedParentDetails
    method __construct (line 9) | public function __construct(
    method ignoring (line 15) | public function ignoring(): bool

FILE: app/Activity/Tools/WebhookFormatter.php
  class WebhookFormatter (line 14) | class WebhookFormatter
    method __construct (line 27) | public function __construct(string $event, Webhook $webhook, string|Lo...
    method format (line 36) | public function format(): array
    method addModelFormatter (line 63) | public function addModelFormatter(callable $condition, callable $forma...
    method addDefaultModelFormatters (line 71) | public function addDefaultModelFormatters(): void
    method formatModel (line 86) | protected function formatModel(Model $model): array
    method formatText (line 99) | protected function formatText(): string
    method getDefault (line 113) | public static function getDefault(string $event, Webhook $webhook, $de...

FILE: app/Activity/WatchLevels.php
  class WatchLevels (line 9) | class WatchLevels
    method all (line 41) | public static function all(): array
    method allSuitedFor (line 55) | public static function allSuitedFor(Entity $entity): array
    method levelNameToValue (line 72) | public static function levelNameToValue(string $level): int
    method levelValueToName (line 81) | public static function levelValueToName(int $level): string

FILE: app/Api/ApiDocsController.php
  class ApiDocsController (line 7) | class ApiDocsController extends ApiController
    method display (line 12) | public function display()
    method json (line 25) | public function json()
    method redirect (line 37) | public function redirect()

FILE: app/Api/ApiDocsGenerator.php
  class ApiDocsGenerator (line 18) | class ApiDocsGenerator
    method generateConsideringCache (line 27) | public static function generateConsideringCache(): Collection
    method generate (line 47) | protected function generate(): Collection
    method loadDetailsFromFiles (line 60) | protected function loadDetailsFromFiles(Collection $routes): Collection
    method loadDetailsFromControllers (line 83) | protected function loadDetailsFromControllers(Collection $routes): Col...
    method getBodyParamsFromClass (line 108) | protected function getBodyParamsFromClass(string $className, string $m...
    method getValidationAsString (line 129) | protected function getValidationAsString($validation): string
    method parseDescriptionFromDocBlockComment (line 151) | protected function parseDescriptionFromDocBlockComment(string $comment...
    method getReflectionMethod (line 165) | protected function getReflectionMethod(string $className, string $meth...
    method getReflectionClass (line 175) | protected function getReflectionClass(string $className): ReflectionClass
    method getFlatApiRoutes (line 189) | protected function getFlatApiRoutes(): Collection

FILE: app/Api/ApiEntityListFormatter.php
  class ApiEntityListFormatter (line 9) | class ApiEntityListFormatter
    method __construct (line 37) | public function __construct(array $list)
    method withField (line 49) | public function withField(string $property, callable $callback): self
    method withType (line 60) | public function withType(): self
    method withTags (line 69) | public function withTags(): self
    method withParents (line 78) | public function withParents(): self
    method format (line 101) | public function format(): array
    method formatSingle (line 115) | protected function formatSingle(Entity $entity): array

FILE: app/Api/ApiToken.php
  class ApiToken (line 22) | class ApiToken extends Model implements Loggable
    method user (line 34) | public function user(): BelongsTo
    method defaultExpiry (line 43) | public static function defaultExpiry(): string
    method logDescriptor (line 51) | public function logDescriptor(): string
    method getUrl (line 59) | public function getUrl(string $path = ''): string

FILE: app/Api/ApiTokenGuard.php
  class ApiTokenGuard (line 15) | class ApiTokenGuard implements Guard
    method __construct (line 39) | public function __construct(Request $request, LoginService $loginService)
    method user (line 48) | public function user()
    method authenticate (line 76) | public function authenticate()
    method getAuthorisedUserFromRequest (line 94) | protected function getAuthorisedUserFromRequest(): Authenticatable
    method validateTokenHeaderValue (line 118) | protected function validateTokenHeaderValue(string $authToken): void
    method validateToken (line 135) | protected function validateToken(?ApiToken $token, string $secret): void
    method validate (line 158) | public function validate(array $credentials = [])
    method logout (line 178) | public function logout()

FILE: app/Api/ListingResponseBuilder.php
  class ListingResponseBuilder (line 11) | class ListingResponseBuilder
    method __construct (line 43) | public function __construct(Builder $query, Request $request, array $f...
    method toResponse (line 53) | public function toResponse(): JsonResponse
    method modifyResults (line 75) | public function modifyResults(callable $modifier): void
    method fetchData (line 83) | protected function fetchData(Builder $query): Collection
    method filterQuery (line 94) | protected function filterQuery(Builder $query): Builder
    method requestFilterToQueryFilter (line 114) | protected function requestFilterToQueryFilter($fieldKey, $value): ?array
    method sortQuery (line 137) | protected function sortQuery(Builder $query): Builder
    method countAndOffsetQuery (line 160) | protected function countAndOffsetQuery(Builder $query): Builder

FILE: app/Api/UserApiTokenController.php
  class UserApiTokenController (line 13) | class UserApiTokenController extends Controller
    method create (line 18) | public function create(Request $request, int $userId)
    method store (line 37) | public function store(Request $request, int $userId)
    method edit (line 73) | public function edit(Request $request, int $userId, int $tokenId)
    method update (line 94) | public function update(Request $request, int $userId, int $tokenId)
    method delete (line 115) | public function delete(int $userId, int $tokenId)
    method destroy (line 130) | public function destroy(int $userId, int $tokenId)
    method checkPermissionAndFetchUserToken (line 145) | protected function checkPermissionAndFetchUserToken(int $userId, int $...
    method updateContext (line 161) | protected function updateContext(Request $request): void
    method getRedirectPath (line 173) | protected function getRedirectPath(User $relatedUser): string

FILE: app/App/AppVersion.php
  class AppVersion (line 5) | class AppVersion
    method get (line 12) | public static function get(): string

FILE: app/App/Application.php
  class Application (line 5) | class Application extends \Illuminate\Foundation\Application
    method configPath (line 14) | public function configPath($path = '')

FILE: app/App/HomeController.php
  class HomeController (line 15) | class HomeController extends Controller
    method __construct (line 17) | public function __construct(
    method index (line 25) | public function index(

FILE: app/App/MailNotification.php
  class MailNotification (line 12) | abstract class MailNotification extends Notification implements ShouldQueue
    method toMail (line 19) | abstract public function toMail(User $notifiable): MailMessage;
    method via (line 28) | public function via($notifiable)
    method newMailMessage (line 36) | protected function newMailMessage(?LocaleDefinition $locale = null): M...

FILE: app/App/MetaController.php
  class MetaController (line 8) | class MetaController extends Controller
    method robots (line 13) | public function robots()
    method notFound (line 30) | public function notFound()
    method favicon (line 40) | public function favicon(FaviconHandler $favicons)
    method pwaManifest (line 49) | public function pwaManifest(PwaManifestBuilder $manifestBuilder)
    method licenses (line 57) | public function licenses()
    method opensearch (line 71) | public function opensearch()

FILE: app/App/Model.php
  class Model (line 7) | class Model extends EloquentModel
    method getRawAttribute (line 15) | public function getRawAttribute(string $key)

FILE: app/App/Providers/AppServiceProvider.php
  class AppServiceProvider (line 23) | class AppServiceProvider extends ServiceProvider
    method register (line 48) | public function register(): void
    method boot (line 58) | public function boot(): void

FILE: app/App/Providers/AuthServiceProvider.php
  class AuthServiceProvider (line 17) | class AuthServiceProvider extends ServiceProvider
    method boot (line 22) | public function boot(): void
    method register (line 60) | public function register(): void

FILE: app/App/Providers/EventServiceProvider.php
  class EventServiceProvider (line 13) | class EventServiceProvider extends ServiceProvider
    method boot (line 33) | public function boot(): void
    method shouldDiscoverEvents (line 41) | public function shouldDiscoverEvents(): bool
    method configureEmailVerification (line 49) | protected function configureEmailVerification(): void

FILE: app/App/Providers/RouteServiceProvider.php
  class RouteServiceProvider (line 14) | class RouteServiceProvider extends ServiceProvider
    method boot (line 28) | public function boot(): void
    method mapWebRoutes (line 43) | protected function mapWebRoutes(): void
    method mapApiRoutes (line 65) | protected function mapApiRoutes(): void
    method configureRateLimiting (line 79) | protected function configureRateLimiting(): void

FILE: app/App/Providers/ThemeServiceProvider.php
  class ThemeServiceProvider (line 11) | class ThemeServiceProvider extends ServiceProvider
    method register (line 16) | public function register(): void
    method boot (line 25) | public function boot(): void

FILE: app/App/Providers/TranslationServiceProvider.php
  class TranslationServiceProvider (line 10) | class TranslationServiceProvider extends BaseProvider
    method register (line 15) | public function register(): void
    method registerLoader (line 43) | protected function registerLoader(): void

FILE: app/App/Providers/ValidationRuleServiceProvider.php
  class ValidationRuleServiceProvider (line 9) | class ValidationRuleServiceProvider extends ServiceProvider
    method boot (line 14) | public function boot(): void

FILE: app/App/Providers/ViewTweaksServiceProvider.php
  class ViewTweaksServiceProvider (line 12) | class ViewTweaksServiceProvider extends ServiceProvider
    method register (line 14) | public function register()
    method boot (line 26) | public function boot(): void

FILE: app/App/PwaManifestBuilder.php
  class PwaManifestBuilder (line 5) | class PwaManifestBuilder
    method build (line 7) | public function build(): array

FILE: app/App/SluggableInterface.php
  type SluggableInterface (line 11) | interface SluggableInterface

FILE: app/App/SystemApiController.php
  class SystemApiController (line 8) | class SystemApiController extends ApiController
    method read (line 14) | public function read(): JsonResponse

FILE: app/App/helpers.php
  function versioned_asset (line 16) | function versioned_asset(string $file = ''): string
  function user (line 34) | function user(): User
  function userCan (line 43) | function userCan(string|Permission $permission, ?Model $ownable = null):...
  function userCanOnAny (line 59) | function userCanOnAny(string|Permission $action, string $entityClass = '...
  function setting (line 71) | function setting(?string $key = null, mixed $default = null): mixed
  function theme_path (line 86) | function theme_path(string $path = ''): ?string

FILE: app/Console/Commands/AssignSortRuleCommand.php
  class AssignSortRuleCommand (line 10) | class AssignSortRuleCommand extends Command
    method handle (line 33) | public function handle(BookSorter $sorter): int
    method listSortRules (line 87) | protected function listSortRules(): int

FILE: app/Console/Commands/CleanupImagesCommand.php
  class CleanupImagesCommand (line 9) | class CleanupImagesCommand extends Command
    method handle (line 31) | public function handle(ImageService $imageService): int
    method showDeletedImages (line 62) | protected function showDeletedImages($paths): void

FILE: app/Console/Commands/ClearActivityCommand.php
  class ClearActivityCommand (line 8) | class ClearActivityCommand extends Command
    method handle (line 27) | public function handle(): int

FILE: app/Console/Commands/ClearRevisionsCommand.php
  class ClearRevisionsCommand (line 8) | class ClearRevisionsCommand extends Command
    method handle (line 29) | public function handle(): int

FILE: app/Console/Commands/ClearViewsCommand.php
  class ClearViewsCommand (line 8) | class ClearViewsCommand extends Command
    method handle (line 27) | public function handle(): int

FILE: app/Console/Commands/CopyShelfPermissionsCommand.php
  class CopyShelfPermissionsCommand (line 9) | class CopyShelfPermissionsCommand extends Command
    method handle (line 31) | public function handle(PermissionsUpdater $permissionsUpdater, Bookshe...

FILE: app/Console/Commands/CreateAdminCommand.php
  class CreateAdminCommand (line 12) | class CreateAdminCommand extends Command
    method handle (line 37) | public function handle(UserRepo $userRepo): int
    method handleInitialAdminIfExists (line 92) | protected function handleInitialAdminIfExists(UserRepo $userRepo, arra...
    method gatherDetails (line 118) | protected function gatherDetails(bool $generatePassword, bool $initial...
    method snakeCaseOptions (line 153) | protected function snakeCaseOptions(): array

FILE: app/Console/Commands/DeleteUsersCommand.php
  class DeleteUsersCommand (line 9) | class DeleteUsersCommand extends Command
    method handle (line 28) | public function handle(UserRepo $userRepo): int

FILE: app/Console/Commands/HandlesSingleUser.php
  type HandlesSingleUser (line 12) | trait HandlesSingleUser
    method fetchProvidedUser (line 19) | private function fetchProvidedUser(): User

FILE: app/Console/Commands/InstallModuleCommand.php
  class InstallModuleCommand (line 14) | class InstallModuleCommand extends Command
    method handle (line 36) | public function handle(): int
    method handleExistingModulesWithSameName (line 98) | protected function handleExistingModulesWithSameName(array $existingMo...
    method getModuleFolder (line 128) | protected function getModuleFolder(string $themeFolder): string|null
    method getThemeFolder (line 148) | protected function getThemeFolder(): string|null
    method validateAndGetModuleInfoFromZip (line 176) | protected function validateAndGetModuleInfoFromZip(ThemeModuleZip $zip...
    method downloadModuleFile (line 198) | protected function downloadModuleFile(string $location): string|null
    method getPathToZip (line 263) | protected function getPathToZip(string $location): string|null
    method cleanup (line 306) | protected function cleanup(): void

FILE: app/Console/Commands/RefreshAvatarCommand.php
  class RefreshAvatarCommand (line 10) | class RefreshAvatarCommand extends Command
    method handle (line 33) | public function handle(UserAvatars $userAvatar): int
    method processUsers (line 60) | private function processUsers(array $users, UserAvatars $userAvatar): int
    method fetchAvatar (line 106) | private function fetchAvatar(UserAvatars $userAvatar, User $user): bool

FILE: app/Console/Commands/RegeneratePermissionsCommand.php
  class RegeneratePermissionsCommand (line 9) | class RegeneratePermissionsCommand extends Command
    method handle (line 29) | public function handle(JointPermissionBuilder $permissionBuilder): int

FILE: app/Console/Commands/RegenerateReferencesCommand.php
  class RegenerateReferencesCommand (line 9) | class RegenerateReferencesCommand extends Command
    method handle (line 29) | public function handle(ReferenceStore $references): int

FILE: app/Console/Commands/RegenerateSearchCommand.php
  class RegenerateSearchCommand (line 10) | class RegenerateSearchCommand extends Command
    method handle (line 30) | public function handle(SearchIndex $searchIndex): int

FILE: app/Console/Commands/ResetMfaCommand.php
  class ResetMfaCommand (line 8) | class ResetMfaCommand extends Command
    method handle (line 32) | public function handle(): int

FILE: app/Console/Commands/UpdateUrlCommand.php
  class UpdateUrlCommand (line 8) | class UpdateUrlCommand extends Command
    method handle (line 30) | public function handle(Connection $db): int
    method replaceValueInTable (line 94) | protected function replaceValueInTable(
    method checkUserOkayToProceed (line 113) | protected function checkUserOkayToProceed(string $oldUrl, string $newU...

FILE: app/Console/Commands/UpgradeDatabaseEncodingCommand.php
  class UpgradeDatabaseEncodingCommand (line 8) | class UpgradeDatabaseEncodingCommand extends Command
    method handle (line 29) | public function handle(): int

FILE: app/Console/Kernel.php
  class Kernel (line 8) | class Kernel extends ConsoleKernel
    method schedule (line 17) | protected function schedule(Schedule $schedule)
    method commands (line 27) | protected function commands()

FILE: app/Entities/BreadcrumbsViewComposer.php
  class BreadcrumbsViewComposer (line 9) | class BreadcrumbsViewComposer
    method __construct (line 11) | public function __construct(
    method compose (line 19) | public function compose(View $view): void

FILE: app/Entities/Controllers/BookApiController.php
  class BookApiController (line 21) | class BookApiController extends ApiController
    method __construct (line 23) | public function __construct(
    method list (line 34) | public function list()
    method create (line 53) | public function create(Request $request)
    method read (line 69) | public function read(string $id)
    method update (line 104) | public function update(Request $request, string $id)
    method delete (line 121) | public function delete(string $id)
    method forJsonDisplay (line 131) | protected function forJsonDisplay(Book $book): Book
    method rules (line 144) | protected function rules(): array

FILE: app/Entities/Controllers/BookController.php
  class BookController (line 29) | class BookController extends Controller
    method __construct (line 31) | public function __construct(
    method index (line 44) | public function index(Request $request)
    method create (line 77) | public function create(?string $shelfSlug = null)
    method store (line 100) | public function store(Request $request, ?string $shelfSlug = null)
    method show (line 130) | public function show(Request $request, ActivityQueries $activities, st...
    method edit (line 166) | public function edit(string $slug)
    method update (line 182) | public function update(Request $request, string $slug)
    method showDelete (line 209) | public function showDelete(string $bookSlug)
    method destroy (line 223) | public function destroy(string $bookSlug)
    method showCopy (line 243) | public function showCopy(string $bookSlug)
    method copy (line 260) | public function copy(Request $request, Cloner $cloner, string $bookSlug)
    method convertToShelf (line 276) | public function convertToShelf(HierarchyTransformer $transformer, stri...

FILE: app/Entities/Controllers/BookshelfApiController.php
  class BookshelfApiController (line 15) | class BookshelfApiController extends ApiController
    method __construct (line 17) | public function __construct(
    method list (line 26) | public function list()
    method create (line 47) | public function create(Request $request)
    method read (line 61) | public function read(string $id)
    method update (line 85) | public function update(Request $request, string $id)
    method delete (line 104) | public function delete(string $id)
    method forJsonDisplay (line 114) | protected function forJsonDisplay(Bookshelf $shelf): Bookshelf
    method rules (line 127) | protected function rules(): array

FILE: app/Entities/Controllers/BookshelfController.php
  class BookshelfController (line 22) | class BookshelfController extends Controller
    method __construct (line 24) | public function __construct(
    method index (line 37) | public function index(Request $request)
    method create (line 72) | public function create()
    method store (line 87) | public function store(Request $request)
    method show (line 108) | public function show(Request $request, ActivityQueries $activities, st...
    method edit (line 156) | public function edit(string $slug)
    method update (line 182) | public function update(Request $request, string $slug)
    method showDelete (line 208) | public function showDelete(string $slug)
    method destroy (line 223) | public function destroy(string $slug)

FILE: app/Entities/Controllers/ChapterApiController.php
  class ChapterApiController (line 16) | class ChapterApiController extends ApiController
    method __construct (line 39) | public function __construct(
    method list (line 49) | public function list()
    method create (line 63) | public function create(Request $request)
    method read (line 79) | public function read(string $id)
    method update (line 101) | public function update(Request $request, string $id)
    method delete (line 130) | public function delete(string $id)
    method forJsonDisplay (line 140) | protected function forJsonDisplay(Chapter $chapter): Chapter

FILE: app/Entities/Controllers/ChapterController.php
  class ChapterController (line 27) | class ChapterController extends Controller
    method __construct (line 29) | public function __construct(
    method create (line 40) | public function create(string $bookSlug)
    method store (line 58) | public function store(Request $request, string $bookSlug)
    method show (line 78) | public function show(string $bookSlug, string $chapterSlug)
    method edit (line 114) | public function edit(string $bookSlug, string $chapterSlug)
    method update (line 129) | public function update(Request $request, string $bookSlug, string $cha...
    method showDelete (line 151) | public function showDelete(string $bookSlug, string $chapterSlug)
    method destroy (line 167) | public function destroy(string $bookSlug, string $chapterSlug)
    method showMove (line 182) | public function showMove(string $bookSlug, string $chapterSlug)
    method move (line 200) | public function move(Request $request, string $bookSlug, string $chapt...
    method showCopy (line 229) | public function showCopy(string $bookSlug, string $chapterSlug)
    method copy (line 247) | public function copy(Request $request, Cloner $cloner, string $bookSlu...
    method convertToBook (line 272) | public function convertToBook(HierarchyTransformer $transformer, strin...

FILE: app/Entities/Controllers/PageApiController.php
  class PageApiController (line 15) | class PageApiController extends ApiController
    method __construct (line 38) | public function __construct(
    method list (line 48) | public function list()
    method create (line 72) | public function create(Request $request)
    method read (line 105) | public function read(string $id)
    method update (line 127) | public function update(Request $request, string $id)
    method delete (line 164) | public function delete(string $id)

FILE: app/Entities/Controllers/PageController.php
  class PageController (line 32) | class PageController extends Controller
    method __construct (line 34) | public function __construct(
    method create (line 47) | public function create(string $bookSlug, ?string $chapterSlug = null)
    method createAsGuest (line 75) | public function createAsGuest(Request $request, string $bookSlug, ?str...
    method editDraft (line 102) | public function editDraft(Request $request, string $bookSlug, int $pag...
    method store (line 119) | public function store(Request $request, string $bookSlug, int $pageId)
    method show (line 139) | public function show(string $bookSlug, string $pageSlug)
    method getPageAjax (line 182) | public function getPageAjax(int $pageId)
    method edit (line 200) | public function edit(Request $request, string $bookSlug, string $pageS...
    method update (line 221) | public function update(Request $request, string $bookSlug, string $pag...
    method saveDraft (line 239) | public function saveDraft(Request $request, int $pageId)
    method redirectFromLink (line 264) | public function redirectFromLink(int $pageId)
    method showDelete (line 276) | public function showDelete(string $bookSlug, string $pageSlug)
    method showDeleteDraft (line 298) | public function showDeleteDraft(string $bookSlug, int $pageId)
    method destroy (line 321) | public function destroy(string $bookSlug, string $pageSlug)
    method destroyDraft (line 338) | public function destroyDraft(string $bookSlug, int $pageId)
    method showRecentlyUpdated (line 359) | public function showRecentlyUpdated()
    method showMove (line 387) | public function showMove(string $bookSlug, string $pageSlug)
    method move (line 405) | public function move(Request $request, string $bookSlug, string $pageS...
    method showCopy (line 434) | public function showCopy(string $bookSlug, string $pageSlug)
    method copy (line 451) | public function copy(Request $request, Cloner $cloner, string $bookSlu...

FILE: app/Entities/Controllers/PageRevisionController.php
  class PageRevisionController (line 21) | class PageRevisionController extends Controller
    method __construct (line 23) | public function __construct(
    method index (line 35) | public function index(Request $request, string $bookSlug, string $page...
    method show (line 66) | public function show(string $bookSlug, string $pageSlug, int $revisionId)
    method changes (line 95) | public function changes(string $bookSlug, string $pageSlug, int $revis...
    method restore (line 130) | public function restore(string $bookSlug, string $pageSlug, int $revis...
    method destroy (line 145) | public function destroy(string $bookSlug, string $pageSlug, int $revId)
    method destroyUserDraft (line 171) | public function destroyUserDraft(string $pageId)

FILE: app/Entities/Controllers/PageTemplateController.php
  class PageTemplateController (line 11) | class PageTemplateController extends Controller
    method __construct (line 13) | public function __construct(
    method list (line 22) | public function list(Request $request)
    method get (line 54) | public function get(int $templateId)

FILE: app/Entities/Controllers/RecycleBinApiController.php
  class RecycleBinApiController (line 16) | class RecycleBinApiController extends ApiController
    method __construct (line 18) | public function __construct()
    method list (line 36) | public function list()
    method restore (line 52) | public function restore(DeletionRepo $deletionRepo, string $deletionId)
    method destroy (line 64) | public function destroy(DeletionRepo $deletionRepo, string $deletionId)
    method listFormatter (line 74) | protected function listFormatter(Deletion $deletion): void
    method withTrashedQuery (line 96) | protected static function withTrashedQuery(Builder $query): void

FILE: app/Entities/Controllers/RecycleBinController.php
  class RecycleBinController (line 13) | class RecycleBinController extends Controller
    method __construct (line 21) | public function __construct()
    method index (line 34) | public function index()
    method showRestore (line 48) | public function showRestore(string $id)
    method restore (line 79) | public function restore(DeletionRepo $deletionRepo, string $id)
    method showDestroy (line 91) | public function showDestroy(string $id)
    method destroy (line 106) | public function destroy(DeletionRepo $deletionRepo, string $id)
    method empty (line 120) | public function empty(TrashCan $trash)

FILE: app/Entities/EntityExistsRule.php
  class EntityExistsRule (line 7) | class EntityExistsRule implements \Stringable
    method __construct (line 9) | public function __construct(
    method __toString (line 14) | public function __toString()

FILE: app/Entities/EntityProvider.php
  class EntityProvider (line 19) | class EntityProvider
    method __construct (line 27) | public function __construct()
    method all (line 42) | public function all(): array
    method get (line 55) | public function get(string $type): Entity
    method getMorphClasses (line 70) | public function getMorphClasses(array $types): array

FILE: app/Entities/Models/Book.php
  class Book (line 29) | class Book extends Entity implements HasDescriptionInterface, HasCoverIn...
    method getUrl (line 42) | public function getUrl(string $path = ''): string
    method pages (line 51) | public function pages(): HasMany
    method directPages (line 59) | public function directPages(): HasMany
    method chapters (line 68) | public function chapters(): HasMany
    method shelves (line 76) | public function shelves(): BelongsToMany
    method getDirectVisibleChildren (line 84) | public function getDirectVisibleChildren(): Collection
    method defaultTemplate (line 92) | public function defaultTemplate(): EntityDefaultTemplate
    method cover (line 97) | public function cover(): BelongsTo
    method coverInfo (line 102) | public function coverInfo(): EntityCover
    method sortRule (line 110) | public function sortRule(): BelongsTo

FILE: app/Entities/Models/BookChild.php
  class BookChild (line 15) | abstract class BookChild extends Entity
    method book (line 21) | public function book(): BelongsTo

FILE: app/Entities/Models/Bookshelf.php
  class Bookshelf (line 15) | class Bookshelf extends Entity implements HasDescriptionInterface, HasCo...
    method books (line 29) | public function books(): BelongsToMany
    method visibleBooks (line 40) | public function visibleBooks(): BelongsToMany
    method getUrl (line 48) | public function getUrl(string $path = ''): string
    method contains (line 56) | public function contains(Book $book): bool
    method appendBook (line 64) | public function appendBook(Book $book): void
    method coverInfo (line 74) | public function coverInfo(): EntityCover
    method cover (line 79) | public function cover(): BelongsTo

FILE: app/Entities/Models/Chapter.php
  class Chapter (line 16) | class Chapter extends BookChild implements HasDescriptionInterface, HasD...
    method pages (line 30) | public function pages(string $dir = 'ASC'): HasMany
    method getUrl (line 38) | public function getUrl(string $path = ''): string
    method getVisiblePages (line 55) | public function getVisiblePages(): Collection
    method defaultTemplate (line 64) | public function defaultTemplate(): EntityDefaultTemplate

FILE: app/Entities/Models/ContainerTrait.php
  type ContainerTrait (line 11) | trait ContainerTrait
    method descriptionInfo (line 13) | public function descriptionInfo(): EntityHtmlDescription
    method relatedData (line 21) | public function relatedData(): HasOne

FILE: app/Entities/Models/DeletableInterface.php
  type DeletableInterface (line 11) | interface DeletableInterface
    method deletions (line 13) | public function deletions(): MorphMany;

FILE: app/Entities/Models/Deletion.php
  class Deletion (line 19) | class Deletion extends Model implements Loggable
    method deletable (line 28) | public function deletable(): MorphTo
    method deleter (line 36) | public function deleter(): BelongsTo
    method createForEntity (line 44) | public static function createForEntity(Entity $entity): self
    method logDescriptor (line 56) | public function logDescriptor(): string
    method getUrl (line 70) | public function getUrl(string $path = 'restore'): string

FILE: app/Entities/Models/Entity.php
  class Entity (line 55) | abstract class Entity extends Model implements
    method save (line 110) | public function save(array $options = []): bool
    method isContainer (line 140) | public function isContainer(): bool
    method scopeVisible (line 150) | public function scopeVisible(Builder $query): Builder
    method scopeWithLastView (line 158) | public function scopeWithLastView(Builder $query)
    method scopeWithViewCount (line 172) | public function scopeWithViewCount(Builder $query): void
    method matches (line 186) | public function matches(self $entity): bool
    method matchesOrContains (line 194) | public function matchesOrContains(self $entity): bool
    method activity (line 214) | public function activity(): MorphMany
    method views (line 223) | public function views(): MorphMany
    method tags (line 231) | public function tags(): MorphMany
    method comments (line 241) | public function comments(bool $orderByCreated = true): MorphMany
    method searchTerms (line 251) | public function searchTerms(): MorphMany
    method permissions (line 259) | public function permissions(): MorphMany
    method hasPermissions (line 267) | public function hasPermissions(): bool
    method jointPermissions (line 275) | public function jointPermissions(): MorphMany
    method ownedBy (line 284) | public function ownedBy(): BelongsTo
    method getOwnerFieldName (line 289) | public function getOwnerFieldName(): string
    method deletions (line 297) | public function deletions(): MorphMany
    method referencesFrom (line 305) | public function referencesFrom(): MorphMany
    method referencesTo (line 313) | public function referencesTo(): MorphMany
    method isA (line 324) | public static function isA(string $type): bool
    method getType (line 332) | public static function getType(): string
    method getShortName (line 342) | public function getShortName(int $length = 25): string
    method getExcerpt (line 354) | public function getExcerpt(int $length = 100): string
    method getUrl (line 368) | abstract public function getUrl(string $path = '/'): string;
    method getParent (line 375) | public function getParent(): ?self
    method rebuildPermissions (line 394) | public function rebuildPermissions(): void
    method indexForSearch (line 402) | public function indexForSearch(): void
    method favourites (line 410) | public function favourites(): MorphMany
    method isFavourite (line 418) | public function isFavourite(): bool
    method watches (line 428) | public function watches(): MorphMany
    method slugHistory (line 436) | public function slugHistory(): MorphMany
    method logDescriptor (line 444) | public function logDescriptor(): string
    method relatedData (line 452) | abstract public function relatedData(): HasOne;
    method getContentsAttributes (line 458) | protected function getContentsAttributes(): array
    method instanceFromType (line 475) | public static function instanceFromType(string $type): self

FILE: app/Entities/Models/EntityContainerData.php
  class EntityContainerData (line 17) | class EntityContainerData extends Model
    method setKeysForSaveQuery (line 34) | public function setKeysForSaveQuery($query): Builder
    method setKeysForSelectQuery (line 45) | protected function setKeysForSelectQuery($query): Builder

FILE: app/Entities/Models/EntityPageData.php
  class EntityPageData (line 10) | class EntityPageData extends Model

FILE: app/Entities/Models/EntityQueryBuilder.php
  class EntityQueryBuilder (line 8) | class EntityQueryBuilder extends Builder
    method __construct (line 13) | public function __construct(QueryBuilder $query)
    method withoutGlobalScope (line 20) | public function withoutGlobalScope($scope): static
    method forceDelete (line 34) | public function forceDelete()

FILE: app/Entities/Models/EntityScope.php
  class EntityScope (line 10) | class EntityScope implements Scope
    method apply (line 15) | public function apply(Builder $builder, Model $model): void

FILE: app/Entities/Models/EntityTable.php
  class EntityTable (line 20) | class EntityTable extends Model
    method scopeVisible (line 29) | public function scopeVisible(Builder $query): Builder
    method jointPermissions (line 37) | public function jointPermissions(): HasMany
    method tags (line 46) | public function tags(): HasMany
    method permissions (line 55) | public function permissions(): HasMany
    method views (line 64) | public function views(): HasMany

FILE: app/Entities/Models/HasCoverInterface.php
  type HasCoverInterface (line 9) | interface HasCoverInterface
    method coverInfo (line 11) | public function coverInfo(): EntityCover;
    method cover (line 17) | public function cover(): BelongsTo;

FILE: app/Entities/Models/HasDefaultTemplateInterface.php
  type HasDefaultTemplateInterface (line 7) | interface HasDefaultTemplateInterface
    method defaultTemplate (line 9) | public function defaultTemplate(): EntityDefaultTemplate;

FILE: app/Entities/Models/HasDescriptionInterface.php
  type HasDescriptionInterface (line 7) | interface HasDescriptionInterface
    method descriptionInfo (line 9) | public function descriptionInfo(): EntityHtmlDescription;

FILE: app/Entities/Models/Page.php
  class Page (line 31) | class Page extends BookChild
    method scopeVisible (line 48) | public function scopeVisible(Builder $query): Builder
    method chapter (line 58) | public function chapter(): BelongsTo
    method hasChapter (line 66) | public function hasChapter(): bool
    method revisions (line 75) | public function revisions(): HasMany
    method currentRevision (line 86) | public function currentRevision(): HasOne
    method allRevisions (line 98) | public function allRevisions(): HasMany
    method attachments (line 106) | public function attachments(): HasMany
    method getUrl (line 114) | public function getUrl(string $path = ''): string
    method getPermalink (line 130) | public function getPermalink(): string
    method forJsonDisplay (line 138) | public function forJsonDisplay(): self
    method relatedData (line 151) | public function relatedData(): HasOne

FILE: app/Entities/Models/PageRevision.php
  class PageRevision (line 32) | class PageRevision extends Model implements Loggable
    method createdBy (line 42) | public function createdBy(): BelongsTo
    method page (line 50) | public function page(): BelongsTo
    method getUrl (line 58) | public function getUrl(string $path = ''): string
    method getPreviousRevision (line 66) | public function getPreviousRevision(): ?PageRevision
    method isA (line 86) | public static function isA(string $type): bool
    method logDescriptor (line 91) | public function logDescriptor(): string

FILE: app/Entities/Models/SlugHistory.php
  class SlugHistory (line 17) | class SlugHistory extends Model
    method jointPermissions (line 23) | public function jointPermissions(): HasMany

FILE: app/Entities/Queries/BookQueries.php
  class BookQueries (line 12) | class BookQueries implements ProvidesEntityQueries
    method start (line 22) | public function start(): Builder
    method findVisibleById (line 27) | public function findVisibleById(int $id): ?Book
    method findVisibleByIdOrFail (line 32) | public function findVisibleByIdOrFail(int $id): Book
    method findVisibleBySlugOrFail (line 37) | public function findVisibleBySlugOrFail(string $slug): Book
    method visibleForList (line 52) | public function visibleForList(): Builder
    method visibleForContent (line 58) | public function visibleForContent(): Builder
    method visibleForListWithCover (line 63) | public function visibleForListWithCover(): Builder
    method recentlyViewedForCurrentUser (line 68) | public function recentlyViewedForCurrentUser(): Builder
    method popularForList (line 76) | public function popularForList(): Builder

FILE: app/Entities/Queries/BookshelfQueries.php
  class BookshelfQueries (line 12) | class BookshelfQueries implements ProvidesEntityQueries
    method start (line 22) | public function start(): Builder
    method findVisibleById (line 27) | public function findVisibleById(int $id): ?Bookshelf
    method findVisibleByIdOrFail (line 32) | public function findVisibleByIdOrFail(int $id): Bookshelf
    method findVisibleBySlugOrFail (line 43) | public function findVisibleBySlugOrFail(string $slug): Bookshelf
    method visibleForList (line 58) | public function visibleForList(): Builder
    method visibleForContent (line 63) | public function visibleForContent(): Builder
    method visibleForListWithCover (line 68) | public function visibleForListWithCover(): Builder
    method recentlyViewedForCurrentUser (line 73) | public function recentlyViewedForCurrentUser(): Builder
    method popularForList (line 81) | public function popularForList(): Builder

FILE: app/Entities/Queries/ChapterQueries.php
  class ChapterQueries (line 12) | class ChapterQueries implements ProvidesEntityQueries
    method start (line 19) | public function start(): Builder
    method findVisibleById (line 24) | public function findVisibleById(int $id): ?Chapter
    method findVisibleByIdOrFail (line 29) | public function findVisibleByIdOrFail(int $id): Chapter
    method findVisibleBySlugsOrFail (line 34) | public function findVisibleBySlugsOrFail(string $bookSlug, string $cha...
    method usingSlugs (line 53) | public function usingSlugs(string $bookSlug, string $chapterSlug): Bui...
    method visibleForList (line 62) | public function visibleForList(): Builder
    method visibleForContent (line 74) | public function visibleForContent(): Builder

FILE: app/Entities/Queries/EntityQueries.php
  class EntityQueries (line 14) | class EntityQueries
    method __construct (line 16) | public function __construct(
    method findVisibleByStringIdentifier (line 31) | public function findVisibleByStringIdentifier(string $identifier): ?En...
    method findVisibleById (line 43) | public function findVisibleById(string $type, int $id): ?Entity
    method findVisibleByOldSlugs (line 52) | public function findVisibleByOldSlugs(string $type, string $slug, stri...
    method visibleForList (line 67) | public function visibleForList(): Builder
    method visibleForListForType (line 92) | public function visibleForListForType(string $entityType): Builder
    method visibleForContentForType (line 103) | public function visibleForContentForType(string $entityType): Builder
    method getQueriesForType (line 109) | protected function getQueriesForType(string $type): ProvidesEntityQueries

FILE: app/Entities/Queries/PageQueries.php
  class PageQueries (line 12) | class PageQueries implements ProvidesEntityQueries
    method start (line 27) | public function start(): Builder
    method findVisibleById (line 32) | public function findVisibleById(int $id): ?Page
    method findVisibleByIdOrFail (line 37) | public function findVisibleByIdOrFail(int $id): Page
    method findVisibleBySlugsOrFail (line 48) | public function findVisibleBySlugsOrFail(string $bookSlug, string $pag...
    method usingSlugs (line 66) | public function usingSlugs(string $bookSlug, string $pageSlug): Builder
    method visibleForList (line 78) | public function visibleForList(): Builder
    method visibleForContent (line 88) | public function visibleForContent(): Builder
    method visibleForChapterList (line 93) | public function visibleForChapterList(int $chapterId): Builder
    method visibleWithContents (line 101) | public function visibleWithContents(): Builder
    method currentUserDraftsForList (line 108) | public function currentUserDraftsForList(): Builder
    method visibleTemplates (line 115) | public function visibleTemplates(bool $includeContents = false): Builder
    method mergeBookSlugForSelect (line 121) | protected function mergeBookSlugForSelect(array $columns): array

FILE: app/Entities/Queries/PageRevisionQueries.php
  class PageRevisionQueries (line 8) | class PageRevisionQueries
    method start (line 10) | public function start(): Builder
    method findLatestVersionBySlugs (line 15) | public function findLatestVersionBySlugs(string $bookSlug, string $pag...
    method findLatestCurrentUserDraftsForPageId (line 28) | public function findLatestCurrentUserDraftsForPageId(int $pageId): ?Pa...
    method latestCurrentUserDraftsForPageId (line 36) | public function latestCurrentUserDraftsForPageId(int $pageId): Builder

FILE: app/Entities/Queries/ProvidesEntityQueries.php
  type ProvidesEntityQueries (line 19) | interface ProvidesEntityQueries
    method start (line 25) | public function start(): Builder;
    method findVisibleById (line 30) | public function findVisibleById(int $id): ?Entity;
    method visibleForList (line 37) | public function visibleForList(): Builder;
    method visibleForContent (line 44) | public function visibleForContent(): Builder;

FILE: app/Entities/Queries/QueryPopular.php
  class QueryPopular (line 12) | class QueryPopular
    method __construct (line 14) | public function __construct(
    method run (line 21) | public function run(int $count, int $page, array $filterModels): Colle...

FILE: app/Entities/Queries/QueryRecentlyViewed.php
  class QueryRecentlyViewed (line 10) | class QueryRecentlyViewed
    method __construct (line 12) | public function __construct(
    method run (line 18) | public function run(int $count, int $page): Collection

FILE: app/Entities/Queries/QueryTopFavourites.php
  class QueryTopFavourites (line 10) | class QueryTopFavourites
    method __construct (line 12) | public function __construct(
    method run (line 18) | public function run(int $count, int $skip = 0)

FILE: app/Entities/Repos/BaseRepo.php
  class BaseRepo (line 21) | class BaseRepo
    method __construct (line 23) | public function __construct(
    method create (line 41) | public function create(Entity $entity, array $input): Entity
    method update (line 77) | public function update(Entity $entity, array $input): Entity
    method updateCoverImage (line 115) | public function updateCoverImage(Entity&HasCoverInterface $entity, ?Up...
    method sortParent (line 136) | public function sortParent(Entity $entity): void
    method updateDescription (line 147) | protected function updateDescription(Entity $entity, array $input): void
    method refreshSlug (line 166) | public function refreshSlug(Entity $entity): void

FILE: app/Entities/Repos/BookRepo.php
  class BookRepo (line 17) | class BookRepo
    method __construct (line 19) | public function __construct(
    method create (line 30) | public function create(array $input): Book
    method update (line 52) | public function update(Book $book, array $input): Book
    method updateCoverImage (line 76) | public function updateCoverImage(Book $book, ?UploadedFile $coverImage...
    method destroy (line 86) | public function destroy(Book $book): void

FILE: app/Entities/Repos/BookshelfRepo.php
  class BookshelfRepo (line 13) | class BookshelfRepo
    method __construct (line 15) | public function __construct(
    method create (line 25) | public function create(array $input, array $bookIds): Bookshelf
    method update (line 39) | public function update(Bookshelf $shelf, array $input, ?array $bookIds...
    method updateBooks (line 61) | protected function updateBooks(Bookshelf $shelf, array $bookIds): void
    method destroy (line 98) | public function destroy(Bookshelf $shelf): void

FILE: app/Entities/Repos/ChapterRepo.php
  class ChapterRepo (line 19) | class ChapterRepo
    method __construct (line 21) | public function __construct(
    method create (line 32) | public function create(array $input, Book $parentBook): Chapter
    method update (line 54) | public function update(Chapter $chapter, array $input): Chapter
    method destroy (line 75) | public function destroy(Chapter $chapter): void
    method move (line 90) | public function move(Chapter $chapter, string $parentIdentifier): Book

FILE: app/Entities/Repos/DeletionRepo.php
  class DeletionRepo (line 10) | class DeletionRepo
    method __construct (line 12) | public function __construct(
    method restore (line 17) | public function restore(int $id): int
    method destroy (line 26) | public function destroy(int $id): int

FILE: app/Entities/Repos/PageRepo.php
  class PageRepo (line 26) | class PageRepo
    method __construct (line 28) | public function __construct(
    method getNewDraftPage (line 42) | public function getNewDraftPage(Entity $parent): Page
    method publishDraft (line 83) | public function publishDraft(Page $draft, array $input): Page
    method setContentFromInput (line 110) | public function setContentFromInput(Page $page, array $input): void
    method update (line 119) | public function update(Page $page, array $input): Page
    method updateTemplateStatusAndContentFromInput (line 151) | protected function updateTemplateStatusAndContentFromInput(Page $page,...
    method updatePageDraft (line 186) | public function updatePageDraft(Page $page, array $input): Page|PageRe...
    method destroy (line 219) | public function destroy(Page $page): void
    method restoreRevision (line 229) | public function restoreRevision(Page $page, int $revisionId): Page
    method move (line 275) | public function move(Page $page, string $parentIdentifier): Entity
    method getNewPriority (line 303) | protected function getNewPriority(Page $page): int

FILE: app/Entities/Repos/RevisionRepo.php
  class RevisionRepo (line 9) | class RevisionRepo
    method __construct (line 11) | public function __construct(
    method deleteDraftsForCurrentUser (line 19) | public function deleteDraftsForCurrentUser(Page $page): void
    method getNewDraftForCurrentUser (line 28) | public function getNewDraftForCurrentUser(Page $page): PageRevision
    method storeNewForPage (line 49) | public function storeNewForPage(Page $page, ?string $summary = null): ...
    method deleteOldRevisions (line 75) | protected function deleteOldRevisions(Page $page): void

FILE: app/Entities/Tools/BookContents.php
  class BookContents (line 12) | class BookContents
    method __construct (line 16) | public function __construct(
    method getLastPriority (line 25) | public function getLastPriority(): int
    method getTree (line 41) | public function getTree(bool $showDrafts = false, bool $renderPages = ...
    method bookChildSortFunc (line 77) | protected function bookChildSortFunc(): callable
    method getPages (line 91) | protected function getPages(bool $showDrafts = false, bool $getPageCon...

FILE: app/Entities/Tools/Cloner.php
  class Cloner (line 22) | class Cloner
    method __construct (line 26) | public function __construct(
    method clonePage (line 39) | public function clonePage(Page $original, Entity $parent, string $newN...
    method createPageClone (line 47) | protected function createPageClone(Page $original, Entity $parent, str...
    method cloneChapter (line 63) | public function cloneChapter(Chapter $original, Book $parent, string $...
    method createChapterClone (line 71) | protected function createChapterClone(Chapter $original, Book $parent,...
    method cloneBook (line 94) | public function cloneBook(Book $original, string $newName): Book
    method createBookClone (line 102) | protected function createBookClone(Book $original, string $newName): Book
    method entityToInputData (line 140) | public function entityToInputData(Entity $entity): array
    method copyEntityPermissions (line 159) | public function copyEntityPermissions(Entity $sourceEntity, Entity $ta...
    method imageToUploadedFile (line 171) | protected function imageToUploadedFile(Image $image): ?UploadedFile
    method entityTagsToInputArray (line 184) | protected function entityTagsToInputArray(Entity $entity): array
    method newReferenceChangeContext (line 196) | protected function newReferenceChangeContext(): ReferenceChangeContext

FILE: app/Entities/Tools/EntityCover.php
  class EntityCover (line 11) | class EntityCover
    method __construct (line 13) | public function __construct(
    method imageQuery (line 18) | protected function imageQuery(): Builder
    method exists (line 26) | public function exists(): bool
    method getImage (line 34) | public function getImage(): Image|null
    method getUrl (line 51) | public function getUrl(int $width = 440, int $height = 250, string|nul...
    method setImage (line 67) | public function setImage(Image|null $image): void

FILE: app/Entities/Tools/EntityDefaultTemplate.php
  class EntityDefaultTemplate (line 10) | class EntityDefaultTemplate
    method __construct (line 12) | public function __construct(
    method setFromId (line 20) | public function setFromId(int $templateId): void
    method get (line 43) | public function get(): Page|null

FILE: app/Entities/Tools/EntityHtmlDescription.php
  class EntityHtmlDescription (line 11) | class EntityHtmlDescription
    method __construct (line 16) | public function __construct(
    method set (line 27) | public function set(string $html, string|null $plaintext = null): void
    method getHtml (line 47) | public function getHtml(bool $raw = false): string
    method getPlain (line 63) | public function getPlain(): string

FILE: app/Entities/Tools/EntityHydrator.php
  class EntityHydrator (line 13) | class EntityHydrator
    method __construct (line 15) | public function __construct(
    method hydrate (line 26) | public function hydrate(array $entities, bool $loadTags = false, bool ...
    method loadTagsIntoModels (line 57) | protected function loadTagsIntoModels(array $entities): void
    method loadParentsIntoModels (line 96) | protected function loadParentsIntoModels(array $entities): void

FILE: app/Entities/Tools/HierarchyTransformer.php
  class HierarchyTransformer (line 14) | class HierarchyTransformer
    method __construct (line 16) | public function __construct(
    method transformChapterToBook (line 29) | public function transformChapterToBook(Chapter $chapter): Book
    method transformBookToShelf (line 53) | public function transformBookToShelf(Book $book): Bookshelf

FILE: app/Entities/Tools/Markdown/CheckboxConverter.php
  class CheckboxConverter (line 8) | class CheckboxConverter implements ConverterInterface
    method convert (line 10) | public function convert(ElementInterface $element): string
    method getSupportedTags (line 24) | public function getSupportedTags(): array

FILE: app/Entities/Tools/Markdown/CustomDivConverter.php
  class CustomDivConverter (line 8) | class CustomDivConverter extends DivConverter
    method convert (line 10) | public function convert(ElementInterface $element): string

FILE: app/Entities/Tools/Markdown/CustomImageConverter.php
  class CustomImageConverter (line 8) | class CustomImageConverter extends ImageConverter
    method convert (line 10) | public function convert(ElementInterface $element): string

FILE: app/Entities/Tools/Markdown/CustomListItemRenderer.php
  class CustomListItemRenderer (line 14) | class CustomListItemRenderer implements NodeRendererInterface
    method __construct (line 18) | public function __construct()
    method render (line 26) | public function render(Node $node, ChildNodeRendererInterface $childRe...
    method startsTaskListItem (line 37) | private function startsTaskListItem(ListItem $block): bool

FILE: app/Entities/Tools/Markdown/CustomParagraphConverter.php
  class CustomParagraphConverter (line 8) | class CustomParagraphConverter extends ParagraphConverter
    method convert (line 10) | public function convert(ElementInterface $element): string

FILE: app/Entities/Tools/Markdown/CustomStrikeThroughExtension.php
  class CustomStrikeThroughExtension (line 10) | class CustomStrikeThroughExtension implements ExtensionInterface
    method register (line 12) | public function register(EnvironmentBuilderInterface $environment): void

FILE: app/Entities/Tools/Markdown/CustomStrikethroughRenderer.php
  class CustomStrikethroughRenderer (line 16) | class CustomStrikethroughRenderer implements NodeRendererInterface
    method render (line 18) | public function render(Node $node, ChildNodeRendererInterface $childRe...

FILE: app/Entities/Tools/Markdown/HtmlToMarkdown.php
  class HtmlToMarkdown (line 20) | class HtmlToMarkdown
    method __construct (line 24) | public function __construct(string $html)
    method convert (line 32) | public function convert(): string
    method prepareHtml (line 43) | protected function prepareHtml(string $html): string
    method getConverterEnvironment (line 55) | protected function getConverterEnvironment(): Environment

FILE: app/Entities/Tools/Markdown/MarkdownToHtml.php
  class MarkdownToHtml (line 14) | class MarkdownToHtml
    method __construct (line 18) | public function __construct(string $markdown)
    method convert (line 23) | public function convert(): string

FILE: app/Entities/Tools/Markdown/SpacedTagFallbackConverter.php
  class SpacedTagFallbackConverter (line 12) | class SpacedTagFallbackConverter implements ConverterInterface
    method convert (line 14) | public function convert(ElementInterface $element): string
    method getSupportedTags (line 19) | public function getSupportedTags(): array

FILE: app/Entities/Tools/MixedEntityListLoader.php
  class MixedEntityListLoader (line 9) | class MixedEntityListLoader
    method __construct (line 11) | public function __construct(
    method loadIntoRelations (line 22) | public function loadIntoRelations(array $relations, string $relationNa...
    method idsByTypeToModelMap (line 52) | protected function idsByTypeToModelMap(array $idsByType, bool $eagerLo...
    method getRelationsToEagerLoad (line 74) | protected function getRelationsToEagerLoad(string $type): array

FILE: app/Entities/Tools/MixedEntityRequestHelper.php
  class MixedEntityRequestHelper (line 8) | class MixedEntityRequestHelper
    method __construct (line 10) | public function __construct(
    method getVisibleEntityFromRequestData (line 21) | public function getVisibleEntityFromRequestData(array $requestData): E...
    method validationRules (line 32) | public function validationRules(): array

FILE: app/Entities/Tools/NextPreviousContentLocator.php
  class NextPreviousContentLocator (line 12) | class NextPreviousContentLocator
    method __construct (line 21) | public function __construct(BookChild $relativeBookItem, Collection $b...
    method getNext (line 31) | public function getNext(): ?Entity
    method getPrevious (line 39) | public function getPrevious(): ?Entity
    method getCurrentIndex (line 47) | protected function getCurrentIndex(): ?int
    method treeToFlatOrderedCollection (line 61) | protected function treeToFlatOrderedCollection(Collection $bookTree): ...

FILE: app/Entities/Tools/PageContent.php
  class PageContent (line 26) | class PageContent
    method __construct (line 30) | public function __construct(
    method setNewHTML (line 39) | public function setNewHTML(string $html, User $updater): void
    method setNewMarkdown (line 57) | public function setNewMarkdown(string $markdown, User $updater): void
    method extractBase64ImagesFromHtml (line 76) | protected function extractBase64ImagesFromHtml(string $htmlText, User ...
    method extractBase64ImagesFromMarkdown (line 103) | protected function extractBase64ImagesFromMarkdown(string $markdown, U...
    method base64ImageUriToUploadedImageUrl (line 136) | protected function base64ImageUriToUploadedImageUrl(string $uri, User ...
    method parseBase64ImageUri (line 181) | protected function parseBase64ImageUri(string $uri): array
    method formatHtml (line 195) | protected function formatHtml(string $htmlText): string
    method updateIdsRecursively (line 224) | protected function updateIdsRecursively(DOMNode $element, int $depth, ...
    method updateLinks (line 245) | protected function updateLinks(HtmlDocument $doc, array $changeMap): void
    method setUniqueId (line 268) | protected function setUniqueId(DOMNode $element, array &$idMap): array
    method toPlainText (line 303) | public function toPlainText(): string
    method render (line 313) | public function render(bool $blankIncludes = false): string
    method handlePostRender (line 352) | protected function handlePostRender(string $html): string
    method getContentCacheKey (line 358) | protected function getContentCacheKey(string $html): string
    method getContentProviderClosure (line 371) | protected function getContentProviderClosure(bool $blankIncludes): Clo...
    method getNavigation (line 405) | public function getNavigation(string $htmlContent): array
    method headerNodesToLevelList (line 421) | protected function headerNodesToLevelList(DOMNodeList $nodeList): array

FILE: app/Entities/Tools/PageEditActivity.php
  class PageEditActivity (line 11) | class PageEditActivity
    method __construct (line 13) | public function __construct(
    method hasActiveEditing (line 21) | public function hasActiveEditing(): bool
    method activeEditingMessage (line 29) | public function activeEditingMessage(): string
    method getWarningMessagesForDraft (line 51) | public function getWarningMessagesForDraft(Page|PageRevision $draft): ...
    method hasPageBeenUpdatedSinceDraftCreated (line 69) | protected function hasPageBeenUpdatedSinceDraftCreated(PageRevision $d...
    method getEditingActiveDraftMessage (line 77) | public function getEditingActiveDraftMessage(PageRevision $draft): string
    method activePageEditingQuery (line 92) | protected function activePageEditingQuery(int $withinMinutes): Builder

FILE: app/Entities/Tools/PageEditorData.php
  class PageEditorData (line 14) | class PageEditorData
    method __construct (line 19) | public function __construct(
    method getViewData (line 27) | public function getViewData(): array
    method getWarnings (line 32) | public function getWarnings(): array
    method build (line 37) | protected function build(): array
    method updateContentForEditor (line 90) | protected function updateContentForEditor(Page $page, PageEditorType $...
    method getEditorType (line 110) | protected function getEditorType(Page $page): PageEditorType

FILE: app/Entities/Tools/PageEditorType.php
  method isHtmlBased (line 13) | public function isHtmlBased(): bool
  method fromRequestValue (line 21) | public static function fromRequestValue(string $value): static|null
  method forPage (line 27) | public static function forPage(Page $page): static|null
  method getSystemDefault (line 32) | public static function getSystemDefault(): static

FILE: app/Entities/Tools/PageIncludeContent.php
  class PageIncludeContent (line 8) | class PageIncludeContent
    method __construct (line 16) | public function __construct(
    method fromHtmlAndTag (line 22) | public static function fromHtmlAndTag(string $html, PageIncludeTag $ta...
    method fromInlineHtml (line 46) | public static function fromInlineHtml(string $html): self
    method isInline (line 57) | public function isInline(): bool
    method isEmpty (line 62) | public function isEmpty(): bool
    method toDomNodes (line 70) | public function toDomNodes(): array
    method toHtml (line 75) | public function toHtml(): string

FILE: app/Entities/Tools/PageIncludeParser.php
  class PageIncludeParser (line 11) | class PageIncludeParser
    method __construct (line 24) | public function __construct(
    method parse (line 34) | public function parse(): int
    method locateAndIsolateIncludeTags (line 68) | protected function locateAndIsolateIncludeTags(): array
    method splitTextNodesAtTags (line 92) | protected function splitTextNodesAtTags(DOMNode $textNode): array
    method replaceNodeWithNodes (line 125) | protected function replaceNodeWithNodes(DOMNode $toReplace, array $rep...
    method moveTagNodeToBesideParent (line 145) | protected function moveTagNodeToBesideParent(PageIncludeTag $tag, DOMN...
    method splitNodeAtChildNode (line 164) | protected function splitNodeAtChildNode(DOMElement $parentNode, DOMNod...
    method getParentParagraph (line 195) | protected function getParentParagraph(DOMNode $parent): ?DOMNode
    method cleanup (line 212) | protected function cleanup(): void

FILE: app/Entities/Tools/PageIncludeTag.php
  class PageIncludeTag (line 7) | class PageIncludeTag
    method __construct (line 9) | public function __construct(
    method getPageId (line 18) | public function getPageId(): int
    method getSectionId (line 26) | public function getSectionId(): string

FILE: app/Entities/Tools/ParentChanger.php
  class ParentChanger (line 9) | class ParentChanger
    method __construct (line 11) | public function __construct(
    method changeBook (line 20) | public function changeBook(BookChild $child, int $newBookId): void

FILE: app/Entities/Tools/PermissionsUpdater.php
  class PermissionsUpdater (line 16) | class PermissionsUpdater
    method updateFromPermissionsForm (line 21) | public function updateFromPermissionsForm(Entity $entity, Request $req...
    method updateFromApiRequestData (line 46) | public function updateFromApiRequestData(Entity $entity, array $data):...
    method updateOwnerFromId (line 80) | protected function updateOwnerFromId(Entity $entity, int $newOwnerId):...
    method formatPermissionsFromRequestToEntityPermissions (line 91) | protected function formatPermissionsFromRequestToEntityPermissions(arr...
    method formatPermissionsFromApiRequestToEntityPermissions (line 107) | protected function formatPermissionsFromApiRequestToEntityPermissions(...
    method filterEntityPermissionDataUponRole (line 123) | protected function filterEntityPermissionDataUponRole(array $entityPer...
    method updateBookPermissionsFromShelf (line 145) | public function updateBookPermissionsFromShelf(Bookshelf $shelf, $chec...

FILE: app/Entities/Tools/ShelfContext.php
  class ShelfContext (line 9) | class ShelfContext
    method __construct (line 13) | public function __construct(
    method getContextualShelfForBook (line 21) | public function getContextualShelfForBook(Book $book): ?Bookshelf
    method setShelfContext (line 38) | public function setShelfContext(int $shelfId): void
    method clearShelfContext (line 46) | public function clearShelfContext(): void

FILE: app/Entities/Tools/SiblingFetcher.php
  class SiblingFetcher (line 13) | class SiblingFetcher
    method __construct (line 15) | public function __construct(
    method fetch (line 24) | public function fetch(string $entityType, int $entityId): Collection

FILE: app/Entities/Tools/SlugGenerator.php
  class SlugGenerator (line 12) | class SlugGenerator
    method generate (line 18) | public function generate(SluggableInterface&Model $model, string $slug...
    method regenerateForEntity (line 31) | public function regenerateForEntity(Entity $entity): string
    method regenerateForUser (line 41) | public function regenerateForUser(User $user): string
    method formatNameAsSlug (line 51) | protected function formatNameAsSlug(string $name): string
    method slugInUse (line 65) | protected function slugInUse(string $slug, SluggableInterface&Model $m...

FILE: app/Entities/Tools/SlugHistory.php
  class SlugHistory (line 13) | class SlugHistory
    method __construct (line 15) | public function __construct(
    method recordForEntity (line 23) | public function recordForEntity(Entity $entity): void
    method recordForBookChildren (line 55) | protected function recordForBookChildren(Book $book): void
    method lookupEntityIdUsingSlugs (line 71) | public function lookupEntityIdUsingSlugs(string $type, string $slug, s...
    method getLatestEntryForEntity (line 89) | protected function getLatestEntryForEntity(Entity $entity): SlugHistor...

FILE: app/Entities/Tools/TrashCan.php
  class TrashCan (line 25) | class TrashCan
    method __construct (line 27) | public function __construct(
    method softDestroyShelf (line 37) | public function softDestroyShelf(Bookshelf $shelf)
    method softDestroyBook (line 49) | public function softDestroyBook(Book $book)
    method softDestroyChapter (line 70) | public function softDestroyChapter(Chapter $chapter, bool $recordDelet...
    method softDestroyPage (line 91) | public function softDestroyPage(Page $page, bool $recordDelete = true)
    method ensureDeletable (line 108) | protected function ensureDeletable(Entity $entity): void
    method destroyShelf (line 142) | protected function destroyShelf(Bookshelf $shelf): int
    method destroyBook (line 157) | protected function destroyBook(Book $book): int
    method destroyChapter (line 185) | protected function destroyChapter(Chapter $chapter): int
    method destroyPage (line 205) | protected function destroyPage(Page $page): int
    method getTrashedCounts (line 236) | public function getTrashedCounts(): array
    method empty (line 254) | public function empty(): int
    method destroyFromDeletion (line 270) | public function destroyFromDeletion(Deletion $deletion): int
    method restoreFromDeletion (line 289) | public function restoreFromDeletion(Deletion $deletion): int
    method autoClearOld (line 317) | public function autoClearOld(): int
    method restoreEntity (line 339) | protected function restoreEntity(Entity $entity): int
    method destroyEntity (line 370) | public function destroyEntity(Entity $entity): int
    method destroyCommonRelations (line 391) | protected function destroyCommonRelations(Entity $entity): void

FILE: app/Exceptions/ApiAuthException.php
  class ApiAuthException (line 7) | class ApiAuthException extends \Exception implements HttpExceptionInterface
    method __construct (line 11) | public function __construct(string $message, int $statusCode = 401)
    method getStatusCode (line 17) | public function getStatusCode(): int
    method getHeaders (line 22) | public function getHeaders(): array

FILE: app/Exceptions/BookStackExceptionHandlerPage.php
  class BookStackExceptionHandlerPage (line 8) | class BookStackExceptionHandlerPage implements ExceptionRenderer
    method render (line 10) | public function render($throwable)
    method safeReturn (line 20) | protected function safeReturn(callable $callback, $default = null)
    method getEnvironment (line 29) | protected function getEnvironment(): array

FILE: app/Exceptions/ConfirmationEmailException.php
  class ConfirmationEmailException (line 5) | class ConfirmationEmailException extends NotifyException

FILE: app/Exceptions/FileUploadException.php
  class FileUploadException (line 5) | class FileUploadException extends PrettyException

FILE: app/Exceptions/Handler.php
  class Handler (line 18) | class Handler extends ExceptionHandler
    method report (line 59) | public function report(Throwable $exception)
    method render (line 69) | public function render($request, Throwable $e): SymfonyResponse
    method prepareForOutOfMemory (line 94) | public function prepareForOutOfMemory(callable $onOutOfMemory): void
    method forgetOutOfMemoryHandler (line 102) | public function forgetOutOfMemoryHandler(): void
    method isApiRequest (line 110) | protected function isApiRequest(Request $request): bool
    method renderApiException (line 118) | protected function renderApiException(Throwable $e): JsonResponse
    method unauthenticated (line 154) | protected function unauthenticated($request, AuthenticationException $...
    method invalidJson (line 168) | protected function invalidJson($request, ValidationException $exceptio...

FILE: app/Exceptions/HttpFetchException.php
  class HttpFetchException (line 7) | class HttpFetchException extends Exception

FILE: app/Exceptions/ImageUploadException.php
  class ImageUploadException (line 5) | class ImageUploadException extends PrettyException

FILE: app/Exceptions/JsonDebugException.php
  class JsonDebugException (line 9) | class JsonDebugException extends Exception implements Responsable
    method __construct (line 16) | public function __construct(array $data)
    method toResponse (line 26) | public function toResponse($request): JsonResponse

FILE: app/Exceptions/LdapException.php
  class LdapException (line 5) | class LdapException extends PrettyException

FILE: app/Exceptions/LoginAttemptEmailNeededException.php
  class LoginAttemptEmailNeededException (line 5) | class LoginAttemptEmailNeededException extends LoginAttemptException

FILE: app/Exceptions/LoginAttemptException.php
  class LoginAttemptException (line 5) | class LoginAttemptException extends \Exception

FILE: app/Exceptions/LoginAttemptInvalidUserException.php
  class LoginAttemptInvalidUserException (line 5) | class LoginAttemptInvalidUserException extends LoginAttemptException

FILE: app/Exceptions/MoveOperationException.php
  class MoveOperationException (line 7) | class MoveOperationException extends Exception

FILE: app/Exceptions/NotFoundException.php
  class NotFoundException (line 5) | class NotFoundException extends PrettyException
    method __construct (line 10) | public function __construct($message = 'Item not found')

FILE: app/Exceptions/NotifyException.php
  class NotifyException (line 9) | class NotifyException extends Exception implements Responsable, HttpExce...
    method __construct (line 15) | public function __construct(string $message, string $redirectLocation ...
    method getStatusCode (line 27) | public function getStatusCode(): int
    method getHeaders (line 35) | public function getHeaders(): array
    method toResponse (line 45) | public function toResponse($request)

FILE: app/Exceptions/PdfExportException.php
  class PdfExportException (line 5) | class PdfExportException extends \Exception

FILE: app/Exceptions/PermissionsException.php
  class PermissionsException (line 7) | class PermissionsException extends Exception

FILE: app/Exceptions/PrettyException.php
  class PrettyException (line 9) | class PrettyException extends Exception implements Responsable, HttpExce...
    method toResponse (line 19) | public function toResponse($request)
    method setSubtitle (line 30) | public function setSubtitle(string $subtitle): self
    method setDetails (line 37) | public function setDetails(string $details): self
    method getStatusCode (line 47) | public function getStatusCode(): int
    method getHeaders (line 55) | public function getHeaders(): array

FILE: app/Exceptions/SamlException.php
  class SamlException (line 5) | class SamlException extends NotifyException

FILE: app/Exceptions/SocialDriverNotConfigured.php
  class SocialDriverNotConfigured (line 5) | class SocialDriverNotConfigured extends PrettyException

FILE: app/Exceptions/SocialSignInAccountNotUsed.php
  class SocialSignInAccountNotUsed (line 5) | class SocialSignInAccountNotUsed extends SocialSignInException

FILE: app/Exceptions/SocialSignInException.php
  class SocialSignInException (line 5) | class SocialSignInException extends NotifyException

FILE: app/Exceptions/StoppedAuthenticationException.php
  class StoppedAuthenticationException (line 10) | class StoppedAuthenticationException extends \Exception implements Respo...
    method __construct (line 12) | public function __construct(
    method toResponse (line 22) | public function toResponse($request)
    method awaitingEmailConfirmationResponse (line 41) | protected function awaitingEmailConfirmationResponse(Request $request)

FILE: app/Exceptions/ThemeException.php
  class ThemeException (line 5) | class ThemeException extends \Exception

FILE: app/Exceptions/UserRegistrationException.php
  class UserRegistrationException (line 5) | class UserRegistrationException extends NotifyException

FILE: app/Exceptions/UserTokenExpiredException.php
  class UserTokenExpiredException (line 5) | class UserTokenExpiredException extends \Exception
    method __construct (line 15) | public function __construct(string $message, int $userId)

FILE: app/Exceptions/UserTokenNotFoundException.php
  class UserTokenNotFoundException (line 5) | class UserTokenNotFoundException extends \Exception

FILE: app/Exceptions/UserUpdateException.php
  class UserUpdateException (line 5) | class UserUpdateException extends NotifyException

FILE: app/Exceptions/ZipExportException.php
  class ZipExportException (line 5) | class ZipExportException extends \Exception

FILE: app/Exceptions/ZipImportException.php
  class ZipImportException (line 5) | class ZipImportException extends \Exception
    method __construct (line 7) | public function __construct(

FILE: app/Exceptions/ZipValidationException.php
  class ZipValidationException (line 5) | class ZipValidationException extends \Exception
    method __construct (line 7) | public function __construct(

FILE: app/Exports/Controllers/BookExportApiController.php
  class BookExportApiController (line 12) | class BookExportApiController extends ApiController
    method __construct (line 14) | public function __construct(
    method exportPdf (line 26) | public function exportPdf(int $id)
    method exportHtml (line 39) | public function exportHtml(int $id)
    method exportPlainText (line 50) | public function exportPlainText(int $id)
    method exportMarkdown (line 61) | public function exportMarkdown(int $id)
    method exportZip (line 72) | public function exportZip(int $id, ZipExportBuilder $builder)

FILE: app/Exports/Controllers/BookExportController.php
  class BookExportController (line 13) | class BookExportController extends Controller
    method __construct (line 15) | public function __construct(
    method pdf (line 28) | public function pdf(string $bookSlug)
    method html (line 41) | public function html(string $bookSlug)
    method plainText (line 52) | public function plainText(string $bookSlug)
    method markdown (line 63) | public function markdown(string $bookSlug)
    method zip (line 75) | public function zip(string $bookSlug, ZipExportBuilder $builder)

FILE: app/Exports/Controllers/ChapterExportApiController.php
  class ChapterExportApiController (line 12) | class ChapterExportApiController extends ApiController
    method __construct (line 14) | public function __construct(
    method exportPdf (line 26) | public function exportPdf(int $id)
    method exportHtml (line 39) | public function exportHtml(int $id)
    method exportPlainText (line 50) | public function exportPlainText(int $id)
    method exportMarkdown (line 61) | public function exportMarkdown(int $id)
    method exportZip (line 72) | public function exportZip(int $id, ZipExportBuilder $builder)

FILE: app/Exports/Controllers/ChapterExportController.php
  class ChapterExportController (line 13) | class ChapterExportController extends Controller
    method __construct (line 15) | public function __construct(
    method pdf (line 29) | public function pdf(string $bookSlug, string $chapterSlug)
    method html (line 43) | public function html(string $bookSlug, string $chapterSlug)
    method plainText (line 56) | public function plainText(string $bookSlug, string $chapterSlug)
    method markdown (line 69) | public function markdown(string $bookSlug, string $chapterSlug)
    method zip (line 81) | public function zip(string $bookSlug, string $chapterSlug, ZipExportBu...

FILE: app/Exports/Controllers/ImportApiController.php
  class ImportApiController (line 17) | class ImportApiController extends ApiController
    method __construct (line 19) | public function __construct(
    method list (line 29) | public function list(): JsonResponse
    method create (line 48) | public function create(Request $request): JsonResponse
    method read (line 70) | public function read(int $id): JsonResponse
    method run (line 85) | public function run(int $id, Request $request): JsonResponse
    method delete (line 112) | public function delete(int $id): Response
    method rules (line 120) | protected function rules(): array
    method formatErrors (line 133) | protected function formatErrors(array $errors): string

FILE: app/Exports/Controllers/ImportController.php
  class ImportController (line 15) | class ImportController extends Controller
    method __construct (line 17) | public function __construct(
    method start (line 27) | public function start()
    method upload (line 42) | public function upload(Request $request)
    method show (line 62) | public function show(int $id)
    method run (line 77) | public function run(int $id, Request $request)
    method delete (line 104) | public function delete(int $id)

FILE: app/Exports/Controllers/PageExportApiController.php
  class PageExportApiController (line 12) | class PageExportApiController extends ApiController
    method __construct (line 14) | public function __construct(
    method exportPdf (line 26) | public function exportPdf(int $id)
    method exportHtml (line 39) | public function exportHtml(int $id)
    method exportPlainText (line 50) | public function exportPlainText(int $id)
    method exportMarkdown (line 61) | public function exportMarkdown(int $id)
    method exportZip (line 72) | public function exportZip(int $id, ZipExportBuilder $builder)

FILE: app/Exports/Controllers/PageExportController.php
  class PageExportController (line 14) | class PageExportController extends Controller
    method __construct (line 16) | public function __construct(
    method pdf (line 31) | public function pdf(string $bookSlug, string $pageSlug)
    method html (line 46) | public function html(string $bookSlug, string $pageSlug)
    method plainText (line 60) | public function plainText(string $bookSlug, string $pageSlug)
    method markdown (line 73) | public function markdown(string $bookSlug, string $pageSlug)
    method zip (line 85) | public function zip(string $bookSlug, string $pageSlug, ZipExportBuild...

FILE: app/Exports/ExportFormatter.php
  class ExportFormatter (line 18) | class ExportFormatter
    method __construct (line 20) | public function __construct(
    method pageToContainedHtml (line 33) | public function pageToContainedHtml(Page $page): string
    method chapterToContainedHtml (line 51) | public function chapterToContainedHtml(Chapter $chapter): string
    method bookToContainedHtml (line 73) | public function bookToContainedHtml(Book $book): string
    method pageToPdf (line 92) | public function pageToPdf(Page $page): string
    method chapterToPdf (line 110) | public function chapterToPdf(Chapter $chapter): string
    method bookToPdf (line 133) | public function bookToPdf(Book $book): string
    method htmlToPdf (line 152) | protected function htmlToPdf(string $html): string
    method openDetailElements (line 168) | protected function openDetailElements(HtmlDocument $doc): void
    method replaceIframesWithLinks (line 181) | protected function replaceIframesWithLinks(HtmlDocument $doc): void
    method containHtml (line 205) | protected function containHtml(string $htmlContent): string
    method pageToPlainText (line 247) | public function pageToPlainText(Page $page, bool $pageRendered = false...
    method chapterToPlainText (line 268) | public function chapterToPlainText(Chapter $chapter): string
    method bookToPlainText (line 284) | public function bookToPlainText(Book $book): string
    method pageToMarkdown (line 305) | public function pageToMarkdown(Page $page): string
    method chapterToMarkdown (line 317) | public function chapterToMarkdown(Chapter $chapter): string
    method bookToMarkdown (line 336) | public function bookToMarkdown(Book $book): string

FILE: app/Exports/Import.php
  class Import (line 27) | class Import extends Model implements Loggable
    method getSizeString (line 33) | public function getSizeString(): string
    method getUrl (line 42) | public function getUrl(string $path = ''): string
    method logDescriptor (line 48) | public function logDescriptor(): string
    method createdBy (line 53) | public function createdBy(): BelongsTo
    method decodeMetadata (line 58) | public function decodeMetadata(): ZipExportBook|ZipExportChapter|ZipEx...

FILE: app/Exports/ImportRepo.php
  class ImportRepo (line 26) | class ImportRepo
    method __construct (line 28) | public function __construct(
    method getVisibleImports (line 38) | public function getVisibleImports(): Collection
    method queryVisible (line 46) | public function queryVisible(): Builder
    method findVisible (line 57) | public function findVisible(int $id): Import
    method storeFromUpload (line 73) | public function storeFromUpload(UploadedFile $file): Import
    method runImport (line 117) | public function runImport(Import $import, ?string $parent = null): Entity
    method deleteImport (line 140) | public function deleteImport(Import $import): void

FILE: app/Exports/PdfGenerator.php
  class PdfGenerator (line 11) | class PdfGenerator
    method fromHtml (line 21) | public function fromHtml(string $html): string
    method getActiveEngine (line 34) | public function getActiveEngine(): string
    method getWkhtmlBinaryPath (line 47) | protected function getWkhtmlBinaryPath(): string
    method renderUsingDomPdf (line 57) | protected function renderUsingDomPdf(string $html): string
    method renderUsingCommand (line 72) | protected function renderUsingCommand(string $html): string
    method renderUsingWkhtml (line 125) | protected function renderUsingWkhtml(string $html): string
    method convertEntities (line 137) | protected function convertEntities(string $subject): string

FILE: app/Exports/ZipExports/Models/ZipExportAttachment.php
  class ZipExportAttachment (line 9) | final class ZipExportAttachment extends ZipExportModel
    method metadataOnly (line 16) | public function metadataOnly(): void
    method fromModel (line 21) | public static function fromModel(Attachment $model, ZipExportFiles $fi...
    method fromModelArray (line 36) | public static function fromModelArray(array $attachmentArray, ZipExpor...
    method validate (line 43) | public static function validate(ZipValidationHelper $context, array $d...
    method fromArray (line 55) | public static function fromArray(array $data): static

FILE: app/Exports/ZipExports/Models/ZipExportBook.php
  class ZipExportBook (line 11) | final class ZipExportBook extends ZipExportModel
    method metadataOnly (line 24) | public function metadataOnly(): void
    method children (line 39) | public function children(): array
    method fromModel (line 53) | public static function fromModel(Book $model, ZipExportFiles $files): ...
    method validate (line 84) | public static function validate(ZipValidationHelper $context, array $d...
    method fromArray (line 104) | public static function fromArray(array $data): static

FILE: app/Exports/ZipExports/Models/ZipExportChapter.php
  class ZipExportChapter (line 10) | final class ZipExportChapter extends ZipExportModel
    method metadataOnly (line 21) | public function metadataOnly(): void
    method children (line 33) | public function children(): array
    method fromModel (line 38) | public static function fromModel(Chapter $model, ZipExportFiles $files...
    method fromModelArray (line 57) | public static function fromModelArray(array $chapterArray, ZipExportFi...
    method validate (line 64) | public static function validate(ZipValidationHelper $context, array $d...
    method fromArray (line 82) | public static function fromArray(array $data): static

FILE: app/Exports/ZipExports/Models/ZipExportImage.php
  class ZipExportImage (line 10) | final class ZipExportImage extends ZipExportModel
    method fromModel (line 17) | public static function fromModel(Image $model, ZipExportFiles $files):...
    method metadataOnly (line 28) | public function metadataOnly(): void
    method validate (line 33) | public static function validate(ZipValidationHelper $context, array $d...
    method fromArray (line 46) | public static function fromArray(array $data): static

FILE: app/Exports/ZipExports/Models/ZipExportModel.php
  class ZipExportModel (line 8) | abstract class ZipExportModel implements JsonSerializable
    method jsonSerialize (line 16) | public function jsonSerialize(): array
    method validate (line 28) | abstract public static function validate(ZipValidationHelper $context,...
    method fromArray (line 33) | abstract public static function fromArray(array $data): static;
    method fromManyArray (line 40) | public static function fromManyArray(array $data): array
    method metadataOnly (line 56) | abstract public function metadataOnly(): void;

FILE: app/Exports/ZipExports/Models/ZipExportPage.php
  class ZipExportPage (line 10) | final class ZipExportPage extends ZipExportModel
    method metadataOnly (line 24) | public function metadataOnly(): void
    method fromModel (line 39) | public static function fromModel(Page $model, ZipExportFiles $files): ...
    method fromModelArray (line 61) | public static function fromModelArray(array $pageArray, ZipExportFiles...
    method validate (line 68) | public static function validate(ZipValidationHelper $context, array $d...
    method fromArray (line 89) | public static function fromArray(array $data): static

FILE: app/Exports/ZipExports/Models/ZipExportTag.php
  class ZipExportTag (line 8) | final class ZipExportTag extends ZipExportModel
    method metadataOnly (line 13) | public function metadataOnly(): void
    method fromModel (line 18) | public static function fromModel(Tag $model): self
    method fromModelArray (line 27) | public static function fromModelArray(array $tagArray): array
    method validate (line 32) | public static function validate(ZipValidationHelper $context, array $d...
    method fromArray (line 42) | public static function fromArray(array $data): static

FILE: app/Exports/ZipExports/ZipExportBuilder.php
  class ZipExportBuilder (line 15) | class ZipExportBuilder
    method __construct (line 19) | public function __construct(
    method buildForPage (line 28) | public function buildForPage(Page $page): string
    method buildForChapter (line 41) | public function buildForChapter(Chapter $chapter): string
    method buildForBook (line 54) | public function buildForBook(Book $book): string
    method build (line 67) | protected function build(): string

FILE: app/Exports/ZipExports/ZipExportFiles.php
  class ZipExportFiles (line 11) | class ZipExportFiles
    method __construct (line 25) | public function __construct(
    method referenceForAttachment (line 36) | public function referenceForAttachment(Attachment $attachment): string
    method referenceForImage (line 57) | public function referenceForImage(Image $image): string
    method getAllFileNames (line 74) | protected function getAllFileNames(): array
    method extractEach (line 87) | public function extractEach(callable $callback): void

FILE: app/Exports/ZipExports/ZipExportReader.php
  class ZipExportReader (line 12) | class ZipExportReader
    method __construct (line 17) | public function __construct(
    method open (line 26) | protected function open(): void
    method close (line 46) | public function close(): void
    method readData (line 57) | public function readData(): array
    method fileExists (line 81) | public function fileExists(string $fileName): bool
    method fileWithinSizeLimit (line 86) | public function fileWithinSizeLimit(string $fileName): bool
    method streamFile (line 100) | public function streamFile(string $fileName)
    method sniffFileMime (line 108) | public function sniffFileMime(string $fileName): string
    method decodeDataToExportModel (line 119) | public function decodeDataToExportModel(): ZipExportBook|ZipExportChap...

FILE: app/Exports/ZipExports/ZipExportReferences.php
  class ZipExportReferences (line 20) | class ZipExportReferences
    method __construct (line 35) | public function __construct(
    method addPage (line 41) | public function addPage(ZipExportPage $page): void
    method addChapter (line 54) | public function addChapter(ZipExportChapter $chapter): void
    method addBook (line 65) | public function addBook(ZipExportBook $book): void
    method buildReferences (line 80) | public function buildReferences(ZipExportFiles $files): void
    method handleModelReference (line 114) | protected function handleModelReference(Model $model, ZipExportModel $...

FILE: app/Exports/ZipExports/ZipExportValidator.php
  class ZipExportValidator (line 10) | class ZipExportValidator
    method __construct (line 12) | public function __construct(
    method validate (line 17) | public function validate(): array
    method flattenModelErrors (line 43) | protected function flattenModelErrors(array $errors, string $keyPrefix...

FILE: app/Exports/ZipExports/ZipFileReferenceRule.php
  class ZipFileReferenceRule (line 8) | class ZipFileReferenceRule implements ValidationRule
    method __construct (line 10) | public function __construct(
    method validate (line 19) | public function validate(string $attribute, mixed $value, Closure $fai...

FILE: app/Exports/ZipExports/ZipImportReferences.php
  class ZipImportReferences (line 19) | class ZipImportReferences
    method __construct (line 45) | public function __construct(
    method addReference (line 53) | protected function addReference(string $type, Model $model, ?int $impo...
    method addPage (line 61) | public function addPage(Page $page, ZipExportPage $exportPage): void
    method addChapter (line 68) | public function addChapter(Chapter $chapter, ZipExportChapter $exportC...
    method addBook (line 75) | public function addBook(Book $book, ZipExportBook $exportBook): void
    method addAttachment (line 82) | public function addAttachment(Attachment $attachment, ?int $importId):...
    method addImage (line 88) | public function addImage(Image $image, ?int $importId): void
    method handleReference (line 94) | protected function handleReference(string $type, int $id): ?string
    method replaceDrawingIdReferences (line 114) | protected function replaceDrawingIdReferences(string $content): string
    method replaceReferences (line 130) | public function replaceReferences(): void
    method images (line 170) | public function images(): array
    method attachments (line 178) | public function attachments(): array

FILE: app/Exports/ZipExports/ZipImportRunner.php
  class ZipImportRunner (line 29) | class ZipImportRunner
    method __construct (line 33) | public function __construct(
    method run (line 51) | public function run(Import $import, ?Entity $parent = null): Entity
    method revertStoredFiles (line 105) | public function revertStoredFiles(): void
    method cleanup (line 120) | protected function cleanup(): void
    method importBook (line 129) | protected function importBook(ZipExportBook $exportBook, ZipExportRead...
    method importChapter (line 164) | protected function importChapter(ZipExportChapter $exportChapter, Book...
    method importPage (line 186) | protected function importPage(ZipExportPage $exportPage, Book|Chapter ...
    method importAttachment (line 210) | protected function importAttachment(ZipExportAttachment $exportAttachm...
    method importImage (line 230) | protected function importImage(ZipExportImage $exportImage, Page $page...
    method exportTagsToInputArray (line 254) | protected function exportTagsToInputArray(array $exportTags): array
    method zipFileToUploadedFile (line 266) | protected function zipFileToUploadedFile(string $fileName, ZipExportRe...
    method ensurePermissionsPermitImport (line 288) | protected function ensurePermissionsPermitImport(ZipExportPage|ZipExpo...
    method getZipPath (line 355) | protected function getZipPath(Import $import): string

FILE: app/Exports/ZipExports/ZipReferenceParser.php
  class ZipReferenceParser (line 16) | class ZipReferenceParser
    method __construct (line 23) | public function __construct(
    method parseLinks (line 35) | public function parseLinks(string $content, callable $handler): string
    method parseReferences (line 65) | public function parseReferences(string $content, callable $handler): s...
    method linkToModel (line 92) | protected function linkToModel(string $link): ?Model
    method getModelResolvers (line 104) | protected function getModelResolvers(): array
    method getLinkRegex (line 125) | protected function getLinkRegex(): string

FILE: app/Exports/ZipExports/ZipUniqueIdRule.php
  class ZipUniqueIdRule (line 8) | class ZipUniqueIdRule implements ValidationRule
    method __construct (line 10) | public function __construct(
    method validate (line 20) | public function validate(string $attribute, mixed $value, Closure $fai...

FILE: app/Exports/ZipExports/ZipValidationHelper.php
  class ZipValidationHelper (line 8) | class ZipValidationHelper
    method __construct (line 19) | public function __construct(
    method validateData (line 25) | public function validateData(array $data, array $rules): array
    method fileReferenceRule (line 36) | public function fileReferenceRule(array $acceptedMimes = []): ZipFileR...
    method uniqueIdRule (line 41) | public function uniqueIdRule(string $type): ZipUniqueIdRule
    method hasIdBeenUsed (line 46) | public function hasIdBeenUsed(string $type, mixed $id): bool
    method validateRelations (line 63) | public function validateRelations(array $relations, string $model): array

FILE: app/Facades/Activity.php
  class Activity (line 10) | class Activity extends Facade
    method getFacadeAccessor (line 17) | protected static function getFacadeAccessor()

FILE: app/Facades/Theme.php
  class Theme (line 8) | class Theme extends Facade
    method getFacadeAccessor (line 15) | protected static function getFacadeAccessor()

FILE: app/Http/ApiController.php
  class ApiController (line 9) | abstract class ApiController extends Controller
    method apiListingResponse (line 23) | protected function apiListingResponse(Builder $query, array $fields, a...
    method getValidationRules (line 38) | public function getValidationRules(): array
    method rules (line 47) | protected function rules(): array

FILE: app/Http/Controller.php
  class Controller (line 17) | abstract class Controller extends BaseController
    method isSignedIn (line 25) | protected function isSignedIn(): bool
    method preventAccessInDemoMode (line 33) | protected function preventAccessInDemoMode(): void
    method setPageTitle (line 43) | public function setPageTitle(string $title): void
    method showPermissionError (line 53) | protected function showPermissionError(string $redirectLocation = '/')...
    method checkPermission (line 63) | protected function checkPermission(string|Permission $permission): void
    method preventGuestAccess (line 73) | protected function preventGuestAccess(): void
    method checkOwnablePermission (line 83) | protected function checkOwnablePermission(string|Permission $permissio...
    method checkPermissionOr (line 94) | protected function checkPermissionOr(string|Permission $permission, ca...
    method checkPermissionOrCurrentUser (line 105) | protected function checkPermissionOrCurrentUser(string|Permission $per...
    method jsonError (line 115) | protected function jsonError(string $messageText = '', int $statusCode...
    method download (line 123) | protected function download(): DownloadResponseFactory
    method showSuccessNotification (line 131) | protected function showSuccessNotification(string $message): void
    method showWarningNotification (line 139) | protected function showWarningNotification(string $message): void
    method showErrorNotification (line 147) | protected function showErrorNotification(string $message): void
    method logActivity (line 155) | protected function logActivity(string $type, string|Loggable $detail =...
    method getImageValidationRules (line 163) | protected function getImageValidationRules(): array
    method redirectToRequest (line 172) | protected function redirectToRequest(Request $request): RedirectResponse

FILE: app/Http/DownloadResponseFactory.php
  class DownloadResponseFactory (line 9) | class DownloadResponseFactory
    method __construct (line 11) | public function __construct(
    method directly (line 19) | public function directly(string $content, string $fileName): Response
    method streamedDirectly (line 27) | public function streamedDirectly($stream, string $fileName, int $fileS...
    method streamedFileDirectly (line 42) | public function streamedFileDirectly(string $filePath, string $fileNam...
    method streamedInline (line 70) | public function streamedInline($stream, string $fileName, int $fileSiz...
    method streamedFileInline (line 87) | public function streamedFileInline(string $filePath, ?string $fileName...
    method getHeaders (line 102) | protected function getHeaders(string $fileName, int $fileSize, string ...

FILE: app/Http/HttpClientHistory.php
  class HttpClientHistory (line 7) | class HttpClientHistory
    method __construct (line 9) | public function __construct(
    method requestCount (line 14) | public function requestCount(): int
    method requestAt (line 19) | public function requestAt(int $index): ?GuzzleRequest
    method latestRequest (line 24) | public function latestRequest(): ?GuzzleRequest
    method all (line 29) | public function all(): array

FILE: app/Http/HttpRequestService.php
  class HttpRequestService (line 13) | class HttpRequestService
    method buildClient (line 20) | public function buildClient(int $timeout, array $options = []): Client...
    method jsonRequest (line 33) | public function jsonRequest(string $method, string $uri, array $data):...
    method mockClient (line 44) | public function mockClient(array $responses = [], bool $pad = true): H...
    method clearMocking (line 66) | public function clearMocking(): void

FILE: app/Http/Kernel.php
  class Kernel (line 7) | class Kernel extends HttpKernel

FILE: app/Http/Middleware/ApiAuthenticate.php
  class ApiAuthenticate (line 10) | class ApiAuthenticate
    method handle (line 17) | public function handle(Request $request, Closure $next)
    method ensureAuthorizedBySessionOrToken (line 31) | protected function ensureAuthorizedBySessionOrToken(Request $request):...
    method sessionUserHasApiAccess (line 59) | protected function sessionUserHasApiAccess(): bool

FILE: app/Http/Middleware/ApplyCspRules.php
  class ApplyCspRules (line 9) | class ApplyCspRules
    method __construct (line 13) | public function __construct(CspService $cspService)
    method handle (line 26) | public function handle($request, Closure $next)

FILE: app/Http/Middleware/Authenticate.php
  class Authenticate (line 8) | class Authenticate
    method handle (line 13) | public function handle(Request $request, Closure $next)

FILE: app/Http/Middleware/AuthenticatedOrPendingMfa.php
  class AuthenticatedOrPendingMfa (line 9) | class AuthenticatedOrPendingMfa
    method __construct (line 14) | public function __construct(LoginService $loginService, MfaSession $mf...
    method handle (line 28) | public function handle($request, Closure $next)

FILE: app/Http/Middleware/CheckEmailConfirmed.php
  class CheckEmailConfirmed (line 20) | class CheckEmailConfirmed
    method __construct (line 24) | public function __construct(EmailConfirmationService $confirmationServ...
    method handle (line 37) | public function handle($request, Closure $next)

FILE: app/Http/Middleware/CheckGuard.php
  class CheckGuard (line 7) | class CheckGuard
    method handle (line 18) | public function handle($request, Closure $next, ...$allowedGuards)

FILE: app/Http/Middleware/CheckUserHasPermission.php
  class CheckUserHasPermission (line 9) | class CheckUserHasPermission
    method handle (line 16) | public function handle(Request $request, Closure $next, string|Permiss...
    method errorResponse (line 25) | protected function errorResponse(Request $request)

FILE: app/Http/Middleware/EncryptCookies.php
  class EncryptCookies (line 7) | class EncryptCookies extends Middleware

FILE: app/Http/Middleware/Localization.php
  class Localization (line 8) | class Localization
    method __construct (line 10) | public function __construct(
    method handle (line 23) | public function handle($request, Closure $next)

FILE: app/Http/Middleware/PreventRequestsDuringMaintenance.php
  class PreventRequestsDuringMaintenance (line 7) | class PreventRequestsDuringMaintenance extends Middleware

FILE: app/Http/Middleware/PreventResponseCaching.php
  class PreventResponseCaching (line 8) | class PreventResponseCaching
    method handle (line 25) | public function handle($request, Closure $next)

FILE: app/Http/Middleware/RedirectIfAuthenticated.php
  class RedirectIfAuthenticated (line 11) | class RedirectIfAuthenticated
    method handle (line 18) | public function handle(Request $request, Closure $next, string ...$gua...

FILE: app/Http/Middleware/RunThemeActions.php
  class RunThemeActions (line 9) | class RunThemeActions
    method handle (line 19) | public function handle($request, Closure $next)

FILE: app/Http/Middleware/StartSessionExtended.php
  class StartSessionExtended (line 14) | class StartSessionExtended extends Middleware
    method storeCurrentUrl (line 26) | protected function storeCurrentUrl(Request $request, $session): void

FILE: app/Http/Middleware/StartSessionIfCookieExists.php
  class StartSessionIfCookieExists (line 8) | class StartSessionIfCookieExists extends Middleware
    method handle (line 13) | public function handle($request, Closure $next)

FILE: app/Http/Middleware/ThrottleApiRequests.php
  class ThrottleApiRequests (line 7) | class ThrottleApiRequests extends Middleware
    method resolveMaxAttempts (line 12) | protected function resolveMaxAttempts($request, $maxAttempts): int

FILE: app/Http/Middleware/TrimStrings.php
  class TrimStrings (line 7) | class TrimStrings extends Middleware

FILE: app/Http/Middleware/TrustHosts.php
  class TrustHosts (line 7) | class TrustHosts extends Middleware
    method hosts (line 14) | public function hosts(): array

FILE: app/Http/Middleware/TrustProxies.php
  class TrustProxies (line 9) | class TrustProxies extends Middleware
    method handle (line 33) | public function handle(Request $request, Closure $next)

FILE: app/Http/Middleware/VerifyCsrfToken.php
  class VerifyCsrfToken (line 7) | class VerifyCsrfToken extends Middleware

FILE: app/Http/RangeSupportedStream.php
  class RangeSupportedStream (line 14) | class RangeSupportedStream
    method __construct (line 23) | public function __construct(
    method sniffMime (line 35) | public function sniffMime(string $extension = ''): string
    method outputAndClose (line 46) | public function outputAndClose(): void
    method getResponseHeaders (line 75) | public function getResponseHeaders(): array
    method getResponseStatus (line 80) | public function getResponseStatus(): int
    method parseRequest (line 85) | protected function parseRequest(Request $request): void
    method getRangeFromRequest (line 109) | protected function getRangeFromRequest(Request $request): ?array

FILE: app/Http/Request.php
  class Request (line 7) | class Request extends LaravelRequest
    method getSchemeAndHttpHost (line 13) | public function getSchemeAndHttpHost(): string
    method getBaseUrl (line 29) | public function getBaseUrl(): string

FILE: app/Permissions/ContentPermissionApiController.php
  class ContentPermissionApiController (line 11) | class ContentPermissionApiController extends ApiController
    method __construct (line 13) | public function __construct(
    method read (line 49) | public function read(string $contentType, string $contentId)
    method update (line 69) | public function update(Request $request, string $contentType, string $...
    method formattedPermissionDataForEntity (line 82) | protected function formattedPermissionDataForEntity(Entity $entity): a...

FILE: app/Permissions/EntityPermissionEvaluator.php
  class EntityPermissionEvaluator (line 10) | class EntityPermissionEvaluator
    method __construct (line 12) | public function __construct(
    method evaluateEntityForUser (line 17) | public function evaluateEntityForUser(Entity $entity, array $userRoleI...
    method evaluatePermitsByType (line 35) | protected function evaluatePermitsByType(array $permitsByType): ?int
    method collapseAndCategorisePermissions (line 55) | protected function collapseAndCategorisePermissions(array $typeIdChain...
    method getPermissionsMapByTypeId (line 81) | protected function getPermissionsMapByTypeId(array $typeIdChain, array...
    method getPermissionsForEntityIdsOfType (line 121) | protected function getPermissionsForEntityIdsOfType(string $type, arra...
    method gatherEntityChainTypeIds (line 139) | protected function gatherEntityChainTypeIds(SimpleEntityData $entity):...
    method isUserSystemAdmin (line 157) | protected function isUserSystemAdmin($userRoleIds): bool

FILE: app/Permissions/JointPermissionBuilder.php
  class JointPermissionBuilder (line 21) | class JointPermissionBuilder
    method __construct (line 23) | public function __construct(
    method rebuildForAll (line 32) | public function rebuildForAll(): void
    method rebuildForEntity (line 54) | public function rebuildForEntity(Entity $entity): void
    method rebuildForRole (line 85) | public function rebuildForRole(Role $role)
    method bookFetchQuery (line 106) | protected function bookFetchQuery(): Builder
    method buildJointPermissionsForBooks (line 122) | protected function buildJointPermissionsForBooks(EloquentCollection $b...
    method buildJointPermissionsForEntities (line 146) | protected function buildJointPermissionsForEntities(array $entities): ...
    method deleteManyJointPermissionsForEntities (line 158) | protected function deleteManyJointPermissionsForEntities(array $entiti...
    method entitiesToSimpleEntities (line 178) | protected function entitiesToSimpleEntities(array $entities): array
    method createManyJointPermissions (line 196) | protected function createManyJointPermissions(array $originalEntities,...
    method entitiesToTypeIdMap (line 239) | protected function entitiesToTypeIdMap(array $entities): array
    method createJointPermissionData (line 258) | protected function createJointPermissionData(SimpleEntityData $entity,...
    method createJointPermissionDataArray (line 283) | protected function createJointPermissionDataArray(SimpleEntityData $en...

FILE: app/Permissions/MassEntityPermissionEvaluator.php
  class MassEntityPermissionEvaluator (line 7) | class MassEntityPermissionEvaluator extends EntityPermissionEvaluator
    method __construct (line 15) | public function __construct(array $entitiesInvolved, string $action)
    method evaluateEntityForRole (line 21) | public function evaluateEntityForRole(SimpleEntityData $entity, int $r...
    method getPermissionMapByTypeIdForChainAndRole (line 34) | protected function getPermissionMapByTypeIdForChainAndRole(array $type...
    method getPermissionMapByTypeIdAndRoleForAllInvolved (line 54) | protected function getPermissionMapByTypeIdAndRoleForAllInvolved(): array

FILE: app/Permissions/Models/EntityPermission.php
  class EntityPermission (line 19) | class EntityPermission extends Model
    method role (line 35) | public function role(): BelongsTo

FILE: app/Permissions/Models/JointPermission.php
  class JointPermission (line 11) | class JointPermission extends Model
    method role (line 19) | public function role(): BelongsTo
    method entity (line 27) | public function entity(): MorphOne

FILE: app/Permissions/Models/RolePermission.php
  class RolePermission (line 13) | class RolePermission extends Model
    method roles (line 18) | public function roles(): BelongsToMany
    method getByName (line 26) | public static function getByName(string $name): ?RolePermission

FILE: app/Permissions/Permission.php
  method genericForEntity (line 124) | public static function genericForEntity(): array
  method middleware (line 138) | public function middleware(): string

FILE: app/Permissions/PermissionApplicator.php
  class PermissionApplicator (line 17) | class PermissionApplicator
    method __construct (line 19) | public function __construct(
    method checkOwnableUserAccess (line 27) | public function checkOwnableUserAccess(Model&OwnableInterface $ownable...
    method hasEntityPermission (line 64) | protected function hasEntityPermission(Entity $entity, array $userRole...
    method checkUserHasEntityPermissionOnAny (line 75) | public function checkUserHasEntityPermissionOnAny(string|Permission $a...
    method restrictEntityQuery (line 99) | public function restrictEntityQuery(Builder $query): Builder
    method restrictDraftsOnPageQuery (line 117) | public function restrictDraftsOnPageQuery(Builder $query): Builder
    method restrictEntityRelationQuery (line 133) | public function restrictEntityRelationQuery(Builder $query, string $ta...
    method filterDeletedFromEntityRelationQuery (line 155) | public function filterDeletedFromEntityRelationQuery(Builder $query, s...
    method restrictPageRelationQuery (line 192) | public function restrictPageRelationQuery(Builder $query, string $tabl...
    method currentUser (line 214) | protected function currentUser(): User
    method getCurrentUserRoleIds (line 224) | protected function getCurrentUserRoleIds(): array
    method ensureValidEntityAction (line 234) | protected function ensureValidEntityAction(string $action): void

FILE: app/Permissions/PermissionFormData.php
  class PermissionFormData (line 9) | class PermissionFormData
    method __construct (line 13) | public function __construct(Entity $entity)
    method permissionsWithRoles (line 21) | public function permissionsWithRoles(): array
    method rolesNotAssigned (line 35) | public function rolesNotAssigned(): array
    method everyoneElseEntityPermission (line 49) | public function everyoneElseEntityPermission(): EntityPermission
    method everyoneElseRole (line 61) | public function everyoneElseRole(): Role

FILE: app/Permissions/PermissionStatus.php
  class PermissionStatus (line 5) | class PermissionStatus

FILE: app/Permissions/PermissionsController.php
  class PermissionsController (line 13) | class PermissionsController extends Controller
    method __construct (line 15) | public function __construct(
    method showForPage (line 24) | public function showForPage(string $bookSlug, string $pageSlug)
    method updateForPage (line 39) | public function updateForPage(Request $request, string $bookSlug, stri...
    method showForChapter (line 56) | public function showForChapter(string $bookSlug, string $chapterSlug)
    method updateForChapter (line 71) | public function updateForChapter(Request $request, string $bookSlug, s...
    method showForBook (line 88) | public function showForBook(string $slug)
    method updateForBook (line 103) | public function updateForBook(Request $request, string $slug)
    method showForShelf (line 120) | public function showForShelf(string $slug)
    method updateForShelf (line 135) | public function updateForShelf(Request $request, string $slug)
    method copyShelfPermissionsToBooks (line 152) | public function copyShelfPermissionsToBooks(string $slug)
    method formRowForRole (line 169) | public function formRowForRole(string $entityType, string $roleId)

FILE: app/Permissions/PermissionsRepo.php
  class PermissionsRepo (line 14) | class PermissionsRepo
    method __construct (line 18) | public function __construct(
    method getAllRoles (line 26) | public function getAllRoles(): Collection
    method getAllRolesExcept (line 34) | public function getAllRolesExcept(Role $role): Collection
    method getRoleById (line 42) | public function getRoleById(int $id): Role
    method saveNewRole (line 50) | public function saveNewRole(array $roleData): Role
    method updateRole (line 71) | public function updateRole($roleId, array $roleData): Role
    method assignRolePermissions (line 93) | protected function assignRolePermissions(Role $role, array $permission...
    method deleteRole (line 128) | public function deleteRole(int $roleId, int $migrateRoleId = 0): void

FILE: app/Permissions/SimpleEntityData.php
  class SimpleEntityData (line 7) | class SimpleEntityData
    method fromEntity (line 15) | public static function fromEntity(Entity $entity): self

FILE: app/References/CrossLinkParser.php
  class CrossLinkParser (line 15) | class CrossLinkParser
    method __construct (line 22) | public function __construct(array $modelResolvers)
    method extractLinkedModels (line 32) | public function extractLinkedModels(string $html): array
    method getLinksFromContent (line 53) | protected function getLinksFromContent(string $html): array
    method linkToModel (line 71) | protected function linkToModel(string $link): ?Model
    method createWithEntityResolvers (line 87) | public static function createWithEntityResolvers(): self

FILE: app/References/ModelResolvers/AttachmentModelResolver.php
  class AttachmentModelResolver (line 7) | class AttachmentModelResolver implements CrossLinkModelResolver
    method resolve (line 9) | public function resolve(string $link): ?Attachment

FILE: app/References/ModelResolvers/BookLinkModelResolver.php
  class BookLinkModelResolver (line 9) | class BookLinkModelResolver implements CrossLinkModelResolver
    method __construct (line 11) | public function __construct(
    method resolve (line 16) | public function resolve(string $link): ?Model

FILE: app/References/ModelResolvers/BookshelfLinkModelResolver.php
  class BookshelfLinkModelResolver (line 9) | class BookshelfLinkModelResolver implements CrossLinkModelResolver
    method __construct (line 11) | public function __construct(
    method resolve (line 15) | public function resolve(string $link): ?Model

FILE: app/References/ModelResolvers/ChapterLinkModelResolver.php
  class ChapterLinkModelResolver (line 9) | class ChapterLinkModelResolver implements CrossLinkModelResolver
    method __construct (line 11) | public function __construct(
    method resolve (line 16) | public function resolve(string $link): ?Model

FILE: app/References/ModelResolvers/CrossLinkModelResolver.php
  type CrossLinkModelResolver (line 7) | interface CrossLinkModelResolver
    method resolve (line 12) | public function resolve(string $link): ?Model;

FILE: app/References/ModelResolvers/ImageModelResolver.php
  class ImageModelResolver (line 8) | class ImageModelResolver implements CrossLinkModelResolver
    method resolve (line 12) | public function resolve(string $link): ?Image
    method getUrlPattern (line 41) | protected function getUrlPattern(): string

FILE: app/References/ModelResolvers/PageLinkModelResolver.php
  class PageLinkModelResolver (line 9) | class PageLinkModelResolver implements CrossLinkModelResolver
    method __construct (line 11) | public function __construct(
    method resolve (line 16) | public function resolve(string $link): ?Model

FILE: app/References/ModelResolvers/PagePermalinkModelResolver.php
  class PagePermalinkModelResolver (line 9) | class PagePermalinkModelResolver implements CrossLinkModelResolver
    method __construct (line 11) | public function __construct(
    method resolve (line 16) | public function resolve(string $link): ?Model

FILE: app/References/Reference.php
  class Reference (line 16) | class Reference extends Model
    method from (line 20) | public function from(): MorphTo
    method to (line 25) | public function to(): MorphTo
    method jointPermissions (line 30) | public function jointPermissions(): HasMany

FILE: app/References/ReferenceChangeContext.php
  class ReferenceChangeContext (line 7) | class ReferenceChangeContext
    method add (line 15) | public function add(Entity $oldEntity, Entity $newEntity): void
    method getNewEntities (line 23) | public function getNewEntities(): array
    method getOldEntities (line 31) | public function getOldEntities(): array
    method getNewForOld (line 36) | public function getNewForOld(Entity $oldEntity): ?Entity

FILE: app/References/ReferenceController.php
  class ReferenceController (line 8) | class ReferenceController extends Controller
    method __construct (line 10) | public function __construct(
    method page (line 19) | public function page(string $bookSlug, string $pageSlug)
    method chapter (line 33) | public function chapter(string $bookSlug, string $chapterSlug)
    method book (line 47) | public function book(string $slug)
    method shelf (line 61) | public function shelf(string $slug)

FILE: app/References/ReferenceFetcher.php
  class ReferenceFetcher (line 11) | class ReferenceFetcher
    method __construct (line 13) | public function __construct(
    method getReferencesToEntity (line 23) | public function getReferencesToEntity(Entity $entity, bool $withConten...
    method getReferenceCountToEntity (line 35) | public function getReferenceCountToEntity(Entity $entity): int
    method queryReferencesToEntity (line 40) | protected function queryReferencesToEntity(Entity $entity): Builder

FILE: app/References/ReferenceStore.php
  class ReferenceStore (line 9) | class ReferenceStore
    method __construct (line 11) | public function __construct(
    method updateForEntity (line 19) | public function updateForEntity(Entity $entity): void
    method updateForAll (line 27) | public function updateForAll(): void
    method updateForEntities (line 43) | protected function updateForEntities(array $entities): void
    method dropReferencesFromEntities (line 76) | protected function dropReferencesFromEntities(array $entities): void

FILE: app/References/ReferenceUpdater.php
  class ReferenceUpdater (line 12) | class ReferenceUpdater
    method __construct (line 14) | public function __construct(
    method updateEntityReferences (line 20) | public function updateEntityReferences(Entity $entity, string $oldLink...
    method changeReferencesUsingContext (line 35) | public function changeReferencesUsingContext(ReferenceChangeContext $c...
    method getReferencesToUpdate (line 76) | protected function getReferencesToUpdate(Entity $entity): array
    method updateReferencesWithinEntity (line 101) | protected function updateReferencesWithinEntity(Entity $entity, string...
    method updateReferencesWithinDescription (line 112) | protected function updateReferencesWithinDescription(Entity&HasDescrip...
    method updateReferencesWithinPage (line 120) | protected function updateReferencesWithinPage(Page $page, string $oldL...
    method updateLinksInMarkdown (line 135) | protected function updateLinksInMarkdown(string $markdown, string $old...
    method updateLinksInHtml (line 150) | protected function updateLinksInHtml(string $html, string $oldLink, st...

FILE: app/Search/Options/ExactSearchOption.php
  class ExactSearchOption (line 5) | class ExactSearchOption extends SearchOption
    method toString (line 7) | public function toString(): string

FILE: app/Search/Options/FilterSearchOption.php
  class FilterSearchOption (line 5) | class FilterSearchOption extends SearchOption
    method __construct (line 9) | public function __construct(
    method toString (line 18) | public function toString(): string
    method getKey (line 25) | public function getKey(): string
    method fromContentString (line 30) | public static function fromContentString(string $value, bool $negated ...

FILE: app/Search/Options/SearchOption.php
  class SearchOption (line 5) | abstract class SearchOption
    method __construct (line 7) | public function __construct(
    method getKey (line 17) | public function getKey(): string|null
    method toString (line 25) | abstract public function toString(): string;

FILE: app/Search/Options/TagSearchOption.php
  class TagSearchOption (line 5) | class TagSearchOption extends SearchOption
    method toString (line 14) | public function toString(): string
    method getParts (line 22) | public function getParts(): array

FILE: app/Search/Options/TermSearchOption.php
  class TermSearchOption (line 5) | class TermSearchOption extends SearchOption
    method toString (line 7) | public function toString(): string

FILE: app/Search/SearchApiController.php
  class SearchApiController (line 13) | class SearchApiController extends ApiController
    method __construct (line 23) | public function __construct(
    method all (line 39) | public function all(Request $request): JsonResponse

FILE: app/Search/SearchController.php
  class SearchController (line 12) | class SearchController extends Controller
    method __construct (line 14) | public function __construct(
    method search (line 23) | public function search(Request $request, SearchResultsFormatter $forma...
    method searchBook (line 50) | public function searchBook(Request $request, int $bookId)
    method searchChapter (line 61) | public function searchChapter(Request $request, int $chapterId)
    method searchForSelector (line 73) | public function searchForSelector(Request $request, QueryPopular $quer...
    method templatesForSelector (line 94) | public function templatesForSelector(Request $request)
    method searchSuggestions (line 120) | public function searchSuggestions(Request $request)
    method searchSiblings (line 137) | public function searchSiblings(Request $request, SiblingFetcher $sibli...

FILE: app/Search/SearchIndex.php
  class SearchIndex (line 14) | class SearchIndex
    method __construct (line 27) | public function __construct(
    method indexEntity (line 35) | public function indexEntity(Entity $entity): void
    method indexEntities (line 47) | public function indexEntities(array $entities): void
    method indexAllEntities (line 68) | public function indexAllEntities(?callable $progressCallback = null): ...
    method deleteEntityTerms (line 100) | public function deleteEntityTerms(Entity $entity): void
    method insertTerms (line 110) | protected function insertTerms(array $terms): void
    method generateTermScoreMapFromText (line 124) | protected function generateTermScoreMapFromText(string $text, float $s...
    method generateTermScoreMapFromHtml (line 141) | protected function generateTermScoreMapFromHtml(string $html): array
    method generateTermScoreMapFromTags (line 182) | protected function generateTermScoreMapFromTags(array $tags): array
    method textToTermCountMap (line 204) | protected function textToTermCountMap(string $text): array
    method entityToTermDataArray (line 248) | protected function entityToTermDataArray(Entity $entity): array
    method mergeTermScoreMaps (line 284) | protected function mergeTermScoreMaps(...$scoreMaps): array

FILE: app/Search/SearchOptionSet.php
  class SearchOptionSet (line 10) | class SearchOptionSet
    method __construct (line 20) | public function __construct(array $options = [])
    method toValueArray (line 25) | public function toValueArray(): array
    method toValueMap (line 30) | public function toValueMap(): array
    method merge (line 40) | public function merge(SearchOptionSet $set): self
    method filterEmpty (line 45) | public function filterEmpty(): self
    method fromValueArray (line 54) | public static function fromValueArray(array $values, string $class): self
    method all (line 63) | public function all(): array
    method negated (line 71) | public function negated(): self
    method nonNegated (line 80) | public function nonNegated(): self
    method limit (line 89) | public function limit(int $limit): self

FILE: app/Search/SearchOptions.php
  class SearchOptions (line 12) | class SearchOptions
    method __construct (line 23) | public function __construct()
    method fromString (line 34) | public static function fromString(string $search): self
    method fromRequest (line 47) | public static function fromRequest(Request $request): self
    method addOptionsFromString (line 99) | protected function addOptionsFromString(string $searchString): void
    method limitOptions (line 158) | protected function limitOptions(): void
    method decodeEscapes (line 175) | protected static function decodeEscapes(string $input): string
    method parseStandardTermString (line 203) | protected static function parseStandardTermString(string $termString):...
    method setFilter (line 227) | public function setFilter(string $filterName, string $filterValue = ''...
    method toString (line 237) | public function toString(): string
    method getAdditionalOptionsString (line 256) | public function getAdditionalOptionsString(): string

FILE: app/Search/SearchResultsFormatter.php
  class SearchResultsFormatter (line 9) | class SearchResultsFormatter
    method format (line 17) | public function format(array $results, SearchOptions $options): void
    method setSearchPreview (line 28) | protected function setSearchPreview(Entity $entity, SearchOptions $opt...
    method highlightTagsContainingTerms (line 58) | protected function highlightTagsContainingTerms(array $tags, array $te...
    method getMatchPositions (line 85) | protected function getMatchPositions(string $text, array $terms): array
    method sortAndMergeMatchPositions (line 113) | protected function sortAndMergeMatchPositions(array $matchPositions): ...
    method formatTextUsingMatchPositions (line 143) | protected function formatTextUsingMatchPositions(array $matchPositions...

FILE: app/Search/SearchRunner.php
  class SearchRunner (line 21) | class SearchRunner
    method __construct (line 28) | public function __construct(
    method searchEntities (line 42) | public function searchEntities(SearchOptions $searchOpts, string $enti...
    method searchBook (line 67) | public function searchBook(int $bookId, string $searchString): Collection
    method searchChapter (line 83) | public function searchChapter(int $chapterId, string $searchString): C...
    method getPageOfDataFromQuery (line 94) | protected function getPageOfDataFromQuery(EloquentBuilder $query, int ...
    method buildQuery (line 110) | protected function buildQuery(SearchOptions $searchOpts, array $entity...
    method applyTermSearch (line 149) | protected function applyTermSearch(EloquentBuilder $entityQuery, Searc...
    method selectForScoredTerms (line 190) | protected function selectForScoredTerms(array $scoredTerms): array
    method getTermAdjustments (line 216) | protected function getTermAdjustments(SearchOptions $options): array
    method rawTermCountsToAdjustments (line 254) | protected function rawTermCountsToAdjustments(array $termCounts): array
    method applyTagSearch (line 274) | protected function applyTagSearch(EloquentBuilder $query, TagSearchOpt...
    method applyNegatableWhere (line 305) | protected function applyNegatableWhere(EloquentBuilder $query, bool $n...
    method filterUpdatedAfter (line 317) | protected function filterUpdatedAfter(EloquentBuilder $query, string $...
    method filterUpdatedBefore (line 323) | protected function filterUpdatedBefore(EloquentBuilder $query, string ...
    method filterCreatedAfter (line 329) | protected function filterCreatedAfter(EloquentBuilder $query, string $...
    method filterCreatedBefore (line 335) | protected function filterCreatedBefore(EloquentBuilder $query, string ...
    method filterCreatedBy (line 341) | protected function filterCreatedBy(EloquentBuilder $query, string $inp...
    method filterUpdatedBy (line 350) | protected function filterUpdatedBy(EloquentBuilder $query, string $inp...
    method filterOwnedBy (line 359) | protected function filterOwnedBy(EloquentBuilder $query, string $input...
    method filterInName (line 368) | protected function filterInName(EloquentBuilder $query, string $input,...
    method filterInTitle (line 373) | protected function filterInTitle(EloquentBuilder $query, string $input...
    method filterInBody (line 378) | protected function filterInBody(EloquentBuilder $query, string $input,...
    method filterIsRestricted (line 386) | protected function filterIsRestricted(EloquentBuilder $query, string $...
    method filterViewedByMe (line 391) | protected function filterViewedByMe(EloquentBuilder $query, string $in...
    method filterNotViewedByMe (line 400) | protected function filterNotViewedByMe(EloquentBuilder $query, string ...
    method filterIsTemplate (line 409) | protected function filterIsTemplate(EloquentBuilder $query, string $in...
    method filterSortBy (line 414) | protected function filterSortBy(EloquentBuilder $query, string $input,...
    method sortByLastCommented (line 425) | protected function sortByLastCommented(EloquentBuilder $query, bool $n...

FILE: app/Search/SearchTerm.php
  class SearchTerm (line 7) | class SearchTerm extends Model
    method entity (line 17) | public function entity()

FILE: app/Search/SearchTextTokenizer.php
  class SearchTextTokenizer (line 11) | class SearchTextTokenizer
    method __construct (line 18) | public function __construct(
    method currentDelimiter (line 28) | public function currentDelimiter(): string
    method previousDelimiter (line 36) | public function previousDelimiter(): string
    method next (line 45) | public function next(): string|false

FILE: app/Settings/AppSettingsStore.php
  class AppSettingsStore (line 9) | class AppSettingsStore
    method __construct (line 11) | public function __construct(
    method storeFromUpdateRequest (line 17) | public function storeFromUpdateRequest(Request $request, string $categ...
    method updateAppIcon (line 26) | protected function updateAppIcon(Request $request): void
    method updateAppLogo (line 59) | protected function updateAppLogo(Request $request): void
    method storeSimpleSettings (line 76) | protected function storeSimpleSettings(Request $request): void
    method destroyExistingSettingImage (line 88) | protected function destroyExistingSettingImage(string $settingKey): void

FILE: app/Settings/MaintenanceController.php
  class MaintenanceController (line 14) | class MaintenanceController extends Controller
    method index (line 19) | public function index(TrashCan $trashCan)
    method cleanupImages (line 36) | public function cleanupImages(Request $request, ImageService $imageSer...
    method sendTestEmail (line 64) | public function sendTestEmail()
    method regenerateReferences (line 83) | public function regenerateReferences(ReferenceStore $referenceStore)

FILE: app/Settings/Setting.php
  class Setting (line 7) | class Setting extends Model

FILE: app/Settings/SettingController.php
  class SettingController (line 12) | class SettingController extends Controller
    method index (line 17) | public function index()
    method category (line 25) | public function category(string $category)
    method update (line 41) | public function update(Request $request, AppSettingsStore $store, stri...
    method ensureCategoryExists (line 57) | protected function ensureCategoryExists(string $category): void

FILE: app/Settings/SettingService.php
  class SettingService (line 13) | class SettingService
    method get (line 21) | public function get(string $key, $default = null): mixed
    method getInteger (line 35) | public function getInteger(string $key, int $default, int $min = 0, in...
    method getFromSession (line 49) | protected function getFromSession(string $key, $default = false)
    method getUser (line 59) | public function getUser(User $user, string $key, $default = null)
    method getForCurrentUser (line 75) | public function getForCurrentUser(string $key, $default = null)
    method getValueFromStore (line 84) | protected function getValueFromStore(string $key): mixed
    method putValueIntoLocalCache (line 97) | protected function putValueIntoLocalCache(string $key, mixed $value): ...
    method localCacheCategory (line 111) | protected function localCacheCategory(string $key): string
    method loadToLocalCache (line 123) | protected function loadToLocalCache(string $cacheCategory): void
    method formatValue (line 152) | protected function formatValue(mixed $value, mixed $default): mixed
    method has (line 172) | public function has(string $key): bool
    method put (line 183) | public function put(string $key, mixed $value): bool
    method formatArrayValue (line 208) | protected function formatArrayValue(array $value): string
    method putUser (line 222) | public function putUser(User $user, string $key, string $value): bool
    method putForCurrentUser (line 238) | public function putForCurrentUser(string $key, string $value): bool
    method userKey (line 246) | protected function userKey(string $userId, string $key = ''): string
    method remove (line 254) | public function remove(string $key): void
    method deleteUserSettings (line 270) | public function deleteUserSettings(string $userId): void
    method getSettingObjectByKey (line 280) | protected function getSettingObjectByKey(string $key): ?Setting
    method flushCache (line 290) | public function flushCache(): void

FILE: app/Settings/StatusController.php
  class StatusController (line 11) | class StatusController extends Controller
    method show (line 16) | public function show()
    method trueWithoutError (line 45) | protected function trueWithoutError(callable $test): bool

FILE: app/Settings/TestEmailNotification.php
  class TestEmailNotification (line 9) | class TestEmailNotification extends MailNotification
    method toMail (line 11) | public function toMail(User $notifiable): MailMessage

FILE: app/Settings/UserNotificationPreferences.php
  class UserNotificationPreferences (line 7) | class UserNotificationPreferences
    method __construct (line 9) | public function __construct(
    method notifyOnOwnPageChanges (line 14) | public function notifyOnOwnPageChanges(): bool
    method notifyOnOwnPageComments (line 19) | public function notifyOnOwnPageComments(): bool
    method notifyOnCommentReplies (line 24) | public function notifyOnCommentReplies(): bool
    method notifyOnCommentMentions (line 29) | public function notifyOnCommentMentions(): bool
    method updateFromSettingsArray (line 34) | public function updateFromSettingsArray(array $settings)
    method getNotificationSetting (line 47) | protected function getNotificationSetting(string $key): bool

FILE: app/Settings/UserShortcutMap.php
  class UserShortcutMap (line 5) | class UserShortcutMap
    method __construct (line 40) | public function __construct(array $map)
    method merge (line 49) | protected function merge(array $map): void
    method getShortcut (line 61) | public function getShortcut(string $id): string
    method toJson (line 69) | public function toJson(): string
    method fromUserPreferences (line 77) | public static function fromUserPreferences(): self

FILE: app/Sorting/BookSortController.php
  class BookSortController (line 14) | class BookSortController extends Controller
    method __construct (line 16) | public function __construct(
    method show (line 24) | public function show(string $bookSlug)
    method showItem (line 40) | public function showItem(string $bookSlug)
    method update (line 52) | public function update(Request $request, BookSorter $sorter, string $b...

FILE: app/Sorting/BookSortMap.php
  class BookSortMap (line 5) | class BookSortMap
    method addItem (line 12) | public function addItem(BookSortMapItem $mapItem): void
    method all (line 20) | public function all(): array
    method fromJson (line 25) | public static function fromJson(string $json): self

FILE: app/Sorting/BookSortMapItem.php
  class BookSortMapItem (line 5) | class BookSortMapItem
    method __construct (line 32) | public function __construct(int $id, int $sort, ?int $parentChapterId,...

FILE: app/Sorting/BookSorter.php
  class BookSorter (line 14) | class BookSorter
    method __construct (line 16) | public function __construct(
    method runBookAutoSortForAllWithSet (line 22) | public function runBookAutoSortForAllWithSet(SortRule $set): void
    method runBookAutoSort (line 36) | public function runBookAutoSort(Book $book): void
    method sortUsingMap (line 86) | public function sortUsingMap(BookSortMap $sortMap): array
    method applySortUpdates (line 126) | protected function applySortUpdates(BookSortMapItem $sortMapItem, arra...
    method isSortChangePermissible (line 185) | protected function isSortChangePermissible(BookSortMapItem $sortMapIte...
    method loadModelsFromSortMap (line 245) | protected function loadModelsFromSortMap(BookSortMap $sortMap): array

FILE: app/Sorting/SortRule.php
  class SortRule (line 20) | class SortRule extends Model implements Loggable
    method getOperations (line 27) | public function getOperations(): array
    method setOperations (line 35) | public function setOperations(array $options): void
    method logDescriptor (line 41) | public function logDescriptor(): string
    method getUrl (line 46) | public function getUrl(): string
    method books (line 51) | public function books(): HasMany
    method allByName (line 56) | public static function allByName(): Collection

FILE: app/Sorting/SortRuleController.php
  class SortRuleController (line 11) | class SortRuleController extends Controller
    method __construct (line 13) | public function __construct()
    method create (line 18) | public function create()
    method store (line 25) | public function store(Request $request)
    method edit (line 47) | public function edit(string $id)
    method update (line 56) | public function update(string $id, Request $request, BookSorter $bookS...
    method destroy (line 83) | public function destroy(string $id, Request $request)

FILE: app/Sorting/SortRuleOperation.php
  method getLabel (line 24) | public function getLabel(): string
  method getSortFunction (line 40) | public function getSortFunction(): callable
  method allExcluding (line 49) | public static function allExcluding(array $operations): array
  method fromSequence (line 63) | public static function fromSequence(string $sequence): array

FILE: app/Sorting/SortSetOperationComparisons.php
  class SortSetOperationComparisons (line 13) | class SortSetOperationComparisons
    method nameAsc (line 15) | public static function nameAsc(Entity $a, Entity $b): int
    method nameDesc (line 20) | public static function nameDesc(Entity $a, Entity $b): int
    method nameNumericAsc (line 25) | public static function nameNumericAsc(Entity $a, Entity $b): int
    method nameNumericDesc (line 38) | public static function nameNumericDesc(Entity $a, Entity $b): int
    method createdDateAsc (line 43) | public static function createdDateAsc(Entity $a, Entity $b): int
    method createdDateDesc (line 48) | public static function createdDateDesc(Entity $a, Entity $b): int
    method updatedDateAsc (line 53) | public static function updatedDateAsc(Entity $a, Entity $b): int
    method updatedDateDesc (line 58) | public static function updatedDateDesc(Entity $a, Entity $b): int
    method chaptersFirst (line 63) | public static function chaptersFirst(Entity $a, Entity $b): int
    method chaptersLast (line 68) | public static function chaptersLast(Entity $a, Entity $b): int

FILE: app/Sorting/SortUrl.php
  class SortUrl (line 10) | class SortUrl
    method __construct (line 12) | public function __construct(
    method withOverrideData (line 19) | public function withOverrideData(array $overrideData = []): self
    method build (line 24) | public function build(): string

FILE: app/Theming/CustomHtmlHeadContentProvider.php
  class CustomHtmlHeadContentProvider (line 11) | class CustomHtmlHeadContentProvider
    method __construct (line 13) | public function __construct(
    method forWeb (line 24) | public function forWeb(): string
    method forExport (line 40) | public function forExport(): string
    method getSourceContent (line 54) | protected function getSourceContent(): string
    method getModuleHeadContent (line 62) | protected function getModuleHeadContent(): string

FILE: app/Theming/ThemeController.php
  class ThemeController (line 10) | class ThemeController extends Controller
    method publicFile (line 15) | public function publicFile(string $theme, string $path): StreamedResponse

FILE: app/Theming/ThemeEvents.php
  class ThemeEvents (line 16) | class ThemeEvents

FILE: app/Theming/ThemeModule.php
  class ThemeModule (line 5) | readonly class ThemeModule
    method __construct (line 7) | public function __construct(
    method fromJson (line 20) | public static function fromJson(array $data, string $folderName): self
    method path (line 49) | public function path($path = ''): string
    method getVersion (line 55) | public function getVersion(): string

FILE: app/Theming/ThemeModuleException.php
  class ThemeModuleException (line 5) | class ThemeModuleException extends \Exception

FILE: app/Theming/ThemeModuleManager.php
  class ThemeModuleManager (line 7) | class ThemeModuleManager
    method __construct (line 12) | public function __construct(
    method getByName (line 20) | public function getByName(string $name): array
    method deleteModuleFolder (line 25) | public function deleteModuleFolder(string $moduleFolderName): void
    method addFromZip (line 45) | public function addFromZip(string $name, ThemeModuleZip $zip): ThemeMo...
    method deleteDirectoryRecursively (line 64) | protected function deleteDirectoryRecursively(string $path): void
    method load (line 81) | public function load(): array
    method loadFromFolder (line 109) | protected function loadFromFolder(string $folderName): ThemeModule|null

FILE: app/Theming/ThemeModuleZip.php
  class ThemeModuleZip (line 7) | readonly class ThemeModuleZip
    method __construct (line 9) | public function __construct(
    method extractTo (line 14) | public function extractTo(string $destinationPath): void
    method getModuleInstance (line 26) | public function getModuleInstance(): ThemeModule
    method getPath (line 52) | public function getPath(): string
    method exists (line 60) | public function exists(): bool
    method getContentsSize (line 78) | public function getContentsSize(): int

FILE: app/Theming/ThemeService.php
  class ThemeService (line 12) | class ThemeService
    method getTheme (line 28) | public function getTheme(): string
    method listen (line 37) | public function listen(string $event, callable $action): void
    method dispatch (line
Copy disabled (too large) Download .json
Condensed preview — 2270 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (12,475K chars).
[
  {
    "path": ".gitattributes",
    "chars": 61,
    "preview": "* text=auto\n*.css linguist-vendored\n*.less linguist-vendored\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "chars": 3804,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 87,
    "preview": "# These are supported funding model platforms\n\ngithub: [ssddanbrown]\nko_fi: ssddanbrown"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/api_request.yml",
    "chars": 749,
    "preview": "name: New API Endpoint or API Ability\ndescription: Request a new endpoint or API feature be added\nlabels: [\":nut_and_bol"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 2209,
    "preview": "name: Bug Report\ndescription: Create a report to help us fix bugs & issues in existing supported functionality\nlabels: ["
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 543,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Discord Chat Support\n    url: https://discord.gg/ztkBqR2\n    about:"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 2343,
    "preview": "name: Feature Request\ndescription: Request a new feature or idea to be added to BookStack\nlabels: [\":hammer: Feature Req"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/language_request.yml",
    "chars": 1260,
    "preview": "name: Language Request\ndescription: Request a new language to be added to Crowdin for you to translate\nlabels: [\":earth_"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/support_request.yml",
    "chars": 2051,
    "preview": "name: Support Request\ndescription: Request support for a specific problem you have not been able to solve yourself\nlabel"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/z_blank_request.yml",
    "chars": 394,
    "preview": "name: Blank Request (Maintainers Only)\ndescription: For maintainers only - Start a blank request\nbody:\n  - type: markdow"
  },
  {
    "path": ".github/SECURITY.md",
    "chars": 1457,
    "preview": "# Security Policy\n\n## Supported Versions\n\nOnly the [latest version](https://github.com/BookStackApp/BookStack/releases) "
  },
  {
    "path": ".github/translators.txt",
    "chars": 17907,
    "preview": "Name :: Languages\n@robertlandes :: German\n@SergioMendolia :: French\n@NakaharaL :: Portuguese, Brazilian\n@ReeseSebastian "
  },
  {
    "path": ".github/workflows/analyse-php.yml",
    "chars": 965,
    "preview": "name: analyse-php\n\non:\n  push:\n    paths:\n      - '**.php'\n  pull_request:\n    paths:\n      - '**.php'\n\njobs:\n  build:\n "
  },
  {
    "path": ".github/workflows/lint-js.yml",
    "chars": 383,
    "preview": "name: lint-js\n\non:\n  push:\n    paths:\n      - '**.js'\n      - '**.json'\n  pull_request:\n    paths:\n      - '**.js'\n     "
  },
  {
    "path": ".github/workflows/lint-php.yml",
    "chars": 422,
    "preview": "name: lint-php\n\non:\n  push:\n    paths:\n      - '**.php'\n  pull_request:\n    paths:\n      - '**.php'\n\njobs:\n  build:\n    "
  },
  {
    "path": ".github/workflows/test-js.yml",
    "chars": 483,
    "preview": "name: test-js\n\non:\n  push:\n    paths:\n      - '**.js'\n      - '**.ts'\n      - '**.json'\n  pull_request:\n    paths:\n     "
  },
  {
    "path": ".github/workflows/test-migrations.yml",
    "chars": 2004,
    "preview": "name: test-migrations\n\non:\n  push:\n    paths:\n      - '**.php'\n      - 'composer.*'\n  pull_request:\n    paths:\n      - '"
  },
  {
    "path": ".github/workflows/test-php.yml",
    "chars": 1842,
    "preview": "name: test-php\n\non:\n  push:\n    paths:\n      - '**.php'\n      - 'composer.*'\n  pull_request:\n    paths:\n      - '**.php'"
  },
  {
    "path": ".gitignore",
    "chars": 443,
    "preview": "/vendor\n/node_modules\n/.vscode\n/composer\n/coverage\nHomestead.yaml\n.env\n.idea\nnpm-debug.log\nyarn-error.log\n/public/dist\n/"
  },
  {
    "path": "LICENSE",
    "chars": 1122,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015-2026, Dan Brown and the BookStack project contributors.\n\nPermission is hereby "
  },
  {
    "path": "app/Access/Controllers/ConfirmEmailController.php",
    "chars": 3703,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Access\\EmailConfirmationService;\nuse BookStack\\Access\\Logi"
  },
  {
    "path": "app/Access/Controllers/ForgotPasswordController.php",
    "chars": 2215,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Activity\\ActivityType;\nuse BookStack\\Http\\Controller;\nuse "
  },
  {
    "path": "app/Access/Controllers/HandlesPartialLogins.php",
    "chars": 580,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Access\\LoginService;\nuse BookStack\\Exceptions\\NotFoundExce"
  },
  {
    "path": "app/Access/Controllers/LoginController.php",
    "chars": 6229,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Access\\LoginService;\nuse BookStack\\Access\\SocialDriverMana"
  },
  {
    "path": "app/Access/Controllers/MfaBackupCodesController.php",
    "chars": 3278,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Access\\LoginService;\nuse BookStack\\Access\\Mfa\\BackupCodeSe"
  },
  {
    "path": "app/Access/Controllers/MfaController.php",
    "chars": 2053,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Access\\Mfa\\MfaValue;\nuse BookStack\\Activity\\ActivityType;\n"
  },
  {
    "path": "app/Access/Controllers/MfaTotpController.php",
    "chars": 3136,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Access\\LoginService;\nuse BookStack\\Access\\Mfa\\MfaSession;\n"
  },
  {
    "path": "app/Access/Controllers/OidcController.php",
    "chars": 2117,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Access\\Oidc\\OidcException;\nuse BookStack\\Access\\Oidc\\OidcS"
  },
  {
    "path": "app/Access/Controllers/RegisterController.php",
    "chars": 2640,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Access\\LoginService;\nuse BookStack\\Access\\RegistrationServ"
  },
  {
    "path": "app/Access/Controllers/ResetPasswordController.php",
    "chars": 3348,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Access\\LoginService;\nuse BookStack\\Activity\\ActivityType;\n"
  },
  {
    "path": "app/Access/Controllers/Saml2Controller.php",
    "chars": 3712,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Access\\Saml2Service;\nuse BookStack\\Http\\Controller;\nuse Il"
  },
  {
    "path": "app/Access/Controllers/SocialController.php",
    "chars": 4769,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Access\\LoginService;\nuse BookStack\\Access\\RegistrationServ"
  },
  {
    "path": "app/Access/Controllers/ThrottlesLogins.php",
    "chars": 2322,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse Illuminate\\Cache\\RateLimiter;\nuse Illuminate\\Http\\Request;\nuse Illum"
  },
  {
    "path": "app/Access/Controllers/UserInviteController.php",
    "chars": 2863,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Controllers;\n\nuse BookStack\\Access\\UserInviteService;\nuse BookStack\\Exceptions\\UserTok"
  },
  {
    "path": "app/Access/EmailConfirmationService.php",
    "chars": 1094,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse BookStack\\Access\\Notifications\\ConfirmEmailNotification;\nuse BookStack\\Exception"
  },
  {
    "path": "app/Access/ExternalBaseUserProvider.php",
    "chars": 1676,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse BookStack\\Users\\Models\\User;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse "
  },
  {
    "path": "app/Access/GroupSyncService.php",
    "chars": 2692,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse BookStack\\Users\\Models\\Role;\nuse BookStack\\Users\\Models\\User;\nuse Illuminate\\Sup"
  },
  {
    "path": "app/Access/Guards/AsyncExternalBaseSessionGuard.php",
    "chars": 832,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Guards;\n\n/**\n * External Auth Session Guard.\n *\n * The login process for external auth"
  },
  {
    "path": "app/Access/Guards/ExternalBaseSessionGuard.php",
    "chars": 6113,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Guards;\n\nuse BookStack\\Access\\RegistrationService;\nuse Illuminate\\Auth\\GuardHelpers;\nu"
  },
  {
    "path": "app/Access/Guards/LdapSessionGuard.php",
    "chars": 4031,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Guards;\n\nuse BookStack\\Access\\LdapService;\nuse BookStack\\Access\\RegistrationService;\nu"
  },
  {
    "path": "app/Access/Ldap.php",
    "chars": 3404,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\n/**\n * Class Ldap\n * An object-orientated thin abstraction wrapper for common PHP LD"
  },
  {
    "path": "app/Access/LdapService.php",
    "chars": 15960,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse BookStack\\Exceptions\\JsonDebugException;\nuse BookStack\\Exceptions\\LdapException;"
  },
  {
    "path": "app/Access/LoginService.php",
    "chars": 7396,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse BookStack\\Access\\Mfa\\MfaSession;\nuse BookStack\\Activity\\ActivityType;\nuse BookSt"
  },
  {
    "path": "app/Access/Mfa/BackupCodeService.php",
    "chars": 1559,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Mfa;\n\nuse Illuminate\\Support\\Str;\n\nclass BackupCodeService\n{\n    /**\n     * Generate a"
  },
  {
    "path": "app/Access/Mfa/MfaSession.php",
    "chars": 1512,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Mfa;\n\nuse BookStack\\Users\\Models\\User;\n\nclass MfaSession\n{\n    /**\n     * Check if MFA"
  },
  {
    "path": "app/Access/Mfa/MfaValue.php",
    "chars": 1906,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Mfa;\n\nuse BookStack\\Users\\Models\\User;\nuse Carbon\\Carbon;\nuse Illuminate\\Database\\Eloq"
  },
  {
    "path": "app/Access/Mfa/TotpService.php",
    "chars": 2073,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Mfa;\n\nuse BaconQrCode\\Renderer\\Color\\Rgb;\nuse BaconQrCode\\Renderer\\Image\\SvgImageBackE"
  },
  {
    "path": "app/Access/Mfa/TotpValidationRule.php",
    "chars": 663,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Mfa;\n\nuse Closure;\nuse Illuminate\\Contracts\\Validation\\ValidationRule;\n\nclass TotpVali"
  },
  {
    "path": "app/Access/Notifications/ConfirmEmailNotification.php",
    "chars": 782,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Notifications;\n\nuse BookStack\\App\\MailNotification;\nuse BookStack\\Users\\Models\\User;\nu"
  },
  {
    "path": "app/Access/Notifications/ResetPasswordNotification.php",
    "chars": 713,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Notifications;\n\nuse BookStack\\App\\MailNotification;\nuse BookStack\\Users\\Models\\User;\nu"
  },
  {
    "path": "app/Access/Notifications/UserInviteNotification.php",
    "chars": 882,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Notifications;\n\nuse BookStack\\App\\MailNotification;\nuse BookStack\\Users\\Models\\User;\nu"
  },
  {
    "path": "app/Access/Oidc/OidcAccessToken.php",
    "chars": 1752,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nuse InvalidArgumentException;\nuse League\\OAuth2\\Client\\Token\\AccessToken;\n\nclas"
  },
  {
    "path": "app/Access/Oidc/OidcException.php",
    "chars": 99,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nuse Exception;\n\nclass OidcException extends Exception\n{\n}\n"
  },
  {
    "path": "app/Access/Oidc/OidcIdToken.php",
    "chars": 4034,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nclass OidcIdToken extends OidcJwtWithClaims implements ProvidesClaims\n{\n    /**"
  },
  {
    "path": "app/Access/Oidc/OidcInvalidKeyException.php",
    "chars": 94,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nclass OidcInvalidKeyException extends \\Exception\n{\n}\n"
  },
  {
    "path": "app/Access/Oidc/OidcInvalidTokenException.php",
    "chars": 111,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nuse Exception;\n\nclass OidcInvalidTokenException extends Exception\n{\n}\n"
  },
  {
    "path": "app/Access/Oidc/OidcIssuerDiscoveryException.php",
    "chars": 114,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nuse Exception;\n\nclass OidcIssuerDiscoveryException extends Exception\n{\n}\n"
  },
  {
    "path": "app/Access/Oidc/OidcJwtSigningKey.php",
    "chars": 3844,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nuse phpseclib3\\Crypt\\Common\\PublicKey;\nuse phpseclib3\\Crypt\\PublicKeyLoader;\nus"
  },
  {
    "path": "app/Access/Oidc/OidcJwtWithClaims.php",
    "chars": 5872,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nclass OidcJwtWithClaims implements ProvidesClaims\n{\n    protected array $header"
  },
  {
    "path": "app/Access/Oidc/OidcOAuthProvider.php",
    "chars": 3701,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nuse League\\OAuth2\\Client\\Grant\\AbstractGrant;\nuse League\\OAuth2\\Client\\Provider"
  },
  {
    "path": "app/Access/Oidc/OidcProviderSettings.php",
    "chars": 6659,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nuse GuzzleHttp\\Psr7\\Request;\nuse Illuminate\\Contracts\\Cache\\Repository;\nuse Inv"
  },
  {
    "path": "app/Access/Oidc/OidcService.php",
    "chars": 11139,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nuse BookStack\\Access\\GroupSyncService;\nuse BookStack\\Access\\LoginService;\nuse B"
  },
  {
    "path": "app/Access/Oidc/OidcUserDetails.php",
    "chars": 2546,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nuse Illuminate\\Support\\Arr;\n\nclass OidcUserDetails\n{\n    public function __cons"
  },
  {
    "path": "app/Access/Oidc/OidcUserinfoResponse.php",
    "chars": 2746,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\nuse Psr\\Http\\Message\\ResponseInterface;\n\nclass OidcUserinfoResponse implements "
  },
  {
    "path": "app/Access/Oidc/ProvidesClaims.php",
    "chars": 315,
    "preview": "<?php\n\nnamespace BookStack\\Access\\Oidc;\n\ninterface ProvidesClaims\n{\n    /**\n     * Fetch a specific claim.\n     * Return"
  },
  {
    "path": "app/Access/RegistrationService.php",
    "chars": 5065,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse BookStack\\Activity\\ActivityType;\nuse BookStack\\Exceptions\\UserRegistrationExcept"
  },
  {
    "path": "app/Access/Saml2Service.php",
    "chars": 11690,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse BookStack\\Exceptions\\JsonDebugException;\nuse BookStack\\Exceptions\\SamlException;"
  },
  {
    "path": "app/Access/SocialAccount.php",
    "chars": 758,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse BookStack\\Activity\\Models\\Loggable;\nuse BookStack\\App\\Model;\nuse BookStack\\Users"
  },
  {
    "path": "app/Access/SocialAuthService.php",
    "chars": 6796,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse BookStack\\Exceptions\\SocialDriverNotConfigured;\nuse BookStack\\Exceptions\\SocialS"
  },
  {
    "path": "app/Access/SocialDriverManager.php",
    "chars": 4719,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse BookStack\\Exceptions\\SocialDriverNotConfigured;\nuse Illuminate\\Support\\Facades\\E"
  },
  {
    "path": "app/Access/UserInviteException.php",
    "chars": 107,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse Exception;\n\nclass UserInviteException extends Exception\n{\n    //\n}\n"
  },
  {
    "path": "app/Access/UserInviteService.php",
    "chars": 817,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse BookStack\\Access\\Notifications\\UserInviteNotification;\nuse BookStack\\Users\\Model"
  },
  {
    "path": "app/Access/UserTokenService.php",
    "chars": 2837,
    "preview": "<?php\n\nnamespace BookStack\\Access;\n\nuse BookStack\\Exceptions\\UserTokenExpiredException;\nuse BookStack\\Exceptions\\UserTok"
  },
  {
    "path": "app/Activity/ActivityQueries.php",
    "chars": 3853,
    "preview": "<?php\n\nnamespace BookStack\\Activity;\n\nuse BookStack\\Activity\\Models\\Activity;\nuse BookStack\\Entities\\Models\\Book;\nuse Bo"
  },
  {
    "path": "app/Activity/ActivityType.php",
    "chars": 2746,
    "preview": "<?php\n\nnamespace BookStack\\Activity;\n\nclass ActivityType\n{\n    const PAGE_CREATE = 'page_create';\n    const PAGE_UPDATE "
  },
  {
    "path": "app/Activity/CommentRepo.php",
    "chars": 4280,
    "preview": "<?php\n\nnamespace BookStack\\Activity;\n\nuse BookStack\\Activity\\Models\\Comment;\nuse BookStack\\Entities\\Models\\Entity;\nuse B"
  },
  {
    "path": "app/Activity/Controllers/AuditLogApiController.php",
    "chars": 918,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Controllers;\n\nuse BookStack\\Activity\\Models\\Activity;\nuse BookStack\\Http\\ApiControll"
  },
  {
    "path": "app/Activity/Controllers/AuditLogController.php",
    "chars": 2439,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Controllers;\n\nuse BookStack\\Activity\\ActivityType;\nuse BookStack\\Activity\\Models\\Act"
  },
  {
    "path": "app/Activity/Controllers/CommentApiController.php",
    "chars": 4787,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace BookStack\\Activity\\Controllers;\n\nuse BookStack\\Activity\\CommentRepo;\nuse Book"
  },
  {
    "path": "app/Activity/Controllers/CommentController.php",
    "chars": 3848,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Controllers;\n\nuse BookStack\\Activity\\CommentRepo;\nuse BookStack\\Activity\\Tools\\Comme"
  },
  {
    "path": "app/Activity/Controllers/FavouriteController.php",
    "chars": 2251,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Controllers;\n\nuse BookStack\\Entities\\Queries\\QueryTopFavourites;\nuse BookStack\\Entit"
  },
  {
    "path": "app/Activity/Controllers/TagController.php",
    "chars": 1864,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Controllers;\n\nuse BookStack\\Activity\\TagRepo;\nuse BookStack\\Http\\Controller;\nuse Boo"
  },
  {
    "path": "app/Activity/Controllers/WatchController.php",
    "chars": 1023,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Controllers;\n\nuse BookStack\\Activity\\Tools\\UserEntityWatchOptions;\nuse BookStack\\Ent"
  },
  {
    "path": "app/Activity/Controllers/WebhookController.php",
    "chars": 4367,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Controllers;\n\nuse BookStack\\Activity\\ActivityType;\nuse BookStack\\Activity\\Models\\Web"
  },
  {
    "path": "app/Activity/DispatchWebhookJob.php",
    "chars": 2743,
    "preview": "<?php\n\nnamespace BookStack\\Activity;\n\nuse BookStack\\Activity\\Models\\Loggable;\nuse BookStack\\Activity\\Models\\Webhook;\nuse"
  },
  {
    "path": "app/Activity/Models/Activity.php",
    "chars": 2260,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Models;\n\nuse BookStack\\App\\Model;\nuse BookStack\\Entities\\Models\\Entity;\nuse BookStac"
  },
  {
    "path": "app/Activity/Models/Comment.php",
    "chars": 3723,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Models;\n\nuse BookStack\\App\\Model;\nuse BookStack\\Permissions\\Models\\JointPermission;\n"
  },
  {
    "path": "app/Activity/Models/Favouritable.php",
    "chars": 232,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\n\ninterface Favouritab"
  },
  {
    "path": "app/Activity/Models/Favourite.php",
    "chars": 777,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Models;\n\nuse BookStack\\App\\Model;\nuse BookStack\\Permissions\\Models\\JointPermission;\n"
  },
  {
    "path": "app/Activity/Models/Loggable.php",
    "chars": 177,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Models;\n\ninterface Loggable\n{\n    /**\n     * Get the string descriptor for this item"
  },
  {
    "path": "app/Activity/Models/MentionHistory.php",
    "chars": 422,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Support\\Carbon;\n\n/**"
  },
  {
    "path": "app/Activity/Models/Tag.php",
    "chars": 1494,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Models;\n\nuse BookStack\\App\\Model;\nuse BookStack\\Permissions\\Models\\JointPermission;\n"
  },
  {
    "path": "app/Activity/Models/View.php",
    "chars": 1522,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Models;\n\nuse BookStack\\App\\Model;\nuse BookStack\\Permissions\\Models\\JointPermission;\n"
  },
  {
    "path": "app/Activity/Models/Viewable.php",
    "chars": 234,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\n\ninterface Viewable\n{"
  },
  {
    "path": "app/Activity/Models/Watch.php",
    "chars": 1181,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Models;\n\nuse BookStack\\Activity\\WatchLevels;\nuse BookStack\\Permissions\\Models\\JointP"
  },
  {
    "path": "app/Activity/Models/Webhook.php",
    "chars": 2260,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Models;\n\nuse BookStack\\Activity\\ActivityType;\nuse Carbon\\Carbon;\nuse Illuminate\\Data"
  },
  {
    "path": "app/Activity/Models/WebhookTrackedEvent.php",
    "chars": 334,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Datab"
  },
  {
    "path": "app/Activity/Notifications/Handlers/BaseNotificationHandler.php",
    "chars": 1809,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\Handlers;\n\nuse BookStack\\Activity\\Models\\Loggable;\nuse BookStack\\Activ"
  },
  {
    "path": "app/Activity/Notifications/Handlers/CommentCreationNotificationHandler.php",
    "chars": 2035,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\Handlers;\n\nuse BookStack\\Activity\\Models\\Activity;\nuse BookStack\\Activ"
  },
  {
    "path": "app/Activity/Notifications/Handlers/CommentMentionNotificationHandler.php",
    "chars": 3458,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\Handlers;\n\nuse BookStack\\Activity\\ActivityType;\nuse BookStack\\Activity"
  },
  {
    "path": "app/Activity/Notifications/Handlers/NotificationHandler.php",
    "chars": 457,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\Handlers;\n\nuse BookStack\\Activity\\Models\\Activity;\nuse BookStack\\Activ"
  },
  {
    "path": "app/Activity/Notifications/Handlers/PageCreationNotificationHandler.php",
    "chars": 896,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\Handlers;\n\nuse BookStack\\Activity\\Models\\Activity;\nuse BookStack\\Activ"
  },
  {
    "path": "app/Activity/Notifications/Handlers/PageUpdateNotificationHandler.php",
    "chars": 1998,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\Handlers;\n\nuse BookStack\\Activity\\ActivityType;\nuse BookStack\\Activity"
  },
  {
    "path": "app/Activity/Notifications/MessageParts/EntityLinkMessageLine.php",
    "chars": 759,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\MessageParts;\n\nuse BookStack\\Entities\\Models\\Entity;\nuse Illuminate\\Co"
  },
  {
    "path": "app/Activity/Notifications/MessageParts/EntityPathMessageLine.php",
    "chars": 911,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\MessageParts;\n\nuse BookStack\\Entities\\Models\\Entity;\nuse Illuminate\\Co"
  },
  {
    "path": "app/Activity/Notifications/MessageParts/LinkedMailMessageLine.php",
    "chars": 881,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\MessageParts;\n\nuse Illuminate\\Contracts\\Support\\Htmlable;\nuse Stringab"
  },
  {
    "path": "app/Activity/Notifications/MessageParts/ListMessageLine.php",
    "chars": 872,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\MessageParts;\n\nuse Illuminate\\Contracts\\Support\\Htmlable;\nuse Stringab"
  },
  {
    "path": "app/Activity/Notifications/Messages/BaseActivityNotification.php",
    "chars": 2099,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\Messages;\n\nuse BookStack\\Activity\\Models\\Loggable;\nuse BookStack\\Activ"
  },
  {
    "path": "app/Activity/Notifications/Messages/CommentCreationNotification.php",
    "chars": 1613,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\Messages;\n\nuse BookStack\\Activity\\Models\\Comment;\nuse BookStack\\Activi"
  },
  {
    "path": "app/Activity/Notifications/Messages/CommentMentionNotification.php",
    "chars": 1620,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\Messages;\n\nuse BookStack\\Activity\\Models\\Comment;\nuse BookStack\\Activi"
  },
  {
    "path": "app/Activity/Notifications/Messages/PageCreationNotification.php",
    "chars": 1368,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\Messages;\n\nuse BookStack\\Activity\\Notifications\\MessageParts\\EntityLin"
  },
  {
    "path": "app/Activity/Notifications/Messages/PageUpdateNotification.php",
    "chars": 1448,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications\\Messages;\n\nuse BookStack\\Activity\\Notifications\\MessageParts\\EntityLin"
  },
  {
    "path": "app/Activity/Notifications/NotificationManager.php",
    "chars": 2147,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Notifications;\n\nuse BookStack\\Activity\\ActivityType;\nuse BookStack\\Activity\\Models\\A"
  },
  {
    "path": "app/Activity/Queries/WebhooksAllPaginatedAndSorted.php",
    "chars": 887,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Queries;\n\nuse BookStack\\Activity\\Models\\Webhook;\nuse BookStack\\Util\\SimpleListOption"
  },
  {
    "path": "app/Activity/TagRepo.php",
    "chars": 4578,
    "preview": "<?php\n\nnamespace BookStack\\Activity;\n\nuse BookStack\\Activity\\Models\\Tag;\nuse BookStack\\Entities\\Models\\Entity;\nuse BookS"
  },
  {
    "path": "app/Activity/Tools/ActivityLogger.php",
    "chars": 3616,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Tools;\n\nuse BookStack\\Activity\\DispatchWebhookJob;\nuse BookStack\\Activity\\Models\\Act"
  },
  {
    "path": "app/Activity/Tools/CommentTree.php",
    "chars": 3660,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Tools;\n\nuse BookStack\\Activity\\Models\\Comment;\nuse BookStack\\Entities\\Models\\Page;\nu"
  },
  {
    "path": "app/Activity/Tools/CommentTreeNode.php",
    "chars": 432,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Tools;\n\nuse BookStack\\Activity\\Models\\Comment;\n\nclass CommentTreeNode\n{\n    public C"
  },
  {
    "path": "app/Activity/Tools/EntityWatchers.php",
    "chars": 2593,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Tools;\n\nuse BookStack\\Activity\\Models\\Watch;\nuse BookStack\\Entities\\Models\\BookChild"
  },
  {
    "path": "app/Activity/Tools/IpFormatter.php",
    "chars": 2026,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Tools;\n\nclass IpFormatter\n{\n    protected string $ip;\n    protected int $precision;\n"
  },
  {
    "path": "app/Activity/Tools/MentionParser.php",
    "chars": 647,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Tools;\n\nuse BookStack\\Util\\HtmlDocument;\nuse DOMElement;\n\nclass MentionParser\n{\n    "
  },
  {
    "path": "app/Activity/Tools/TagClassGenerator.php",
    "chars": 2177,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Tools;\n\nuse BookStack\\Activity\\Models\\Tag;\nuse BookStack\\Entities\\Models\\BookChild;\n"
  },
  {
    "path": "app/Activity/Tools/UserEntityWatchOptions.php",
    "chars": 3745,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Tools;\n\nuse BookStack\\Activity\\Models\\Watch;\nuse BookStack\\Activity\\WatchLevels;\nuse"
  },
  {
    "path": "app/Activity/Tools/WatchedParentDetails.php",
    "chars": 318,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Tools;\n\nuse BookStack\\Activity\\WatchLevels;\n\nclass WatchedParentDetails\n{\n    public"
  },
  {
    "path": "app/Activity/Tools/WebhookFormatter.php",
    "chars": 3809,
    "preview": "<?php\n\nnamespace BookStack\\Activity\\Tools;\n\nuse BookStack\\Activity\\ActivityType;\nuse BookStack\\Activity\\Models\\Loggable;"
  },
  {
    "path": "app/Activity/WatchLevels.php",
    "chars": 2026,
    "preview": "<?php\n\nnamespace BookStack\\Activity;\n\nuse BookStack\\Entities\\Models\\Bookshelf;\nuse BookStack\\Entities\\Models\\Entity;\nuse"
  },
  {
    "path": "app/Api/ApiDocsController.php",
    "chars": 900,
    "preview": "<?php\n\nnamespace BookStack\\Api;\n\nuse BookStack\\Http\\ApiController;\n\nclass ApiDocsController extends ApiController\n{\n    "
  },
  {
    "path": "app/Api/ApiDocsGenerator.php",
    "chars": 7207,
    "preview": "<?php\n\nnamespace BookStack\\Api;\n\nuse BookStack\\App\\AppVersion;\nuse BookStack\\Http\\ApiController;\nuse Exception;\nuse Illu"
  },
  {
    "path": "app/Api/ApiEntityListFormatter.php",
    "chars": 3631,
    "preview": "<?php\n\nnamespace BookStack\\Api;\n\nuse BookStack\\Entities\\Models\\BookChild;\nuse BookStack\\Entities\\Models\\Entity;\nuse Book"
  },
  {
    "path": "app/Api/ApiToken.php",
    "chars": 1457,
    "preview": "<?php\n\nnamespace BookStack\\Api;\n\nuse BookStack\\Activity\\Models\\Loggable;\nuse BookStack\\Users\\Models\\User;\nuse Illuminate"
  },
  {
    "path": "app/Api/ApiTokenGuard.php",
    "chars": 4760,
    "preview": "<?php\n\nnamespace BookStack\\Api;\n\nuse BookStack\\Access\\LoginService;\nuse BookStack\\Exceptions\\ApiAuthException;\nuse BookS"
  },
  {
    "path": "app/Api/ListingResponseBuilder.php",
    "chars": 4643,
    "preview": "<?php\n\nnamespace BookStack\\Api;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Collection;\n"
  },
  {
    "path": "app/Api/UserApiTokenController.php",
    "chars": 5719,
    "preview": "<?php\n\nnamespace BookStack\\Api;\n\nuse BookStack\\Activity\\ActivityType;\nuse BookStack\\Http\\Controller;\nuse BookStack\\Permi"
  },
  {
    "path": "app/App/AppVersion.php",
    "chars": 505,
    "preview": "<?php\n\nnamespace BookStack\\App;\n\nclass AppVersion\n{\n    protected static string $version = '';\n\n    /**\n     * Get the a"
  },
  {
    "path": "app/App/Application.php",
    "chars": 543,
    "preview": "<?php\n\nnamespace BookStack\\App;\n\nclass Application extends \\Illuminate\\Foundation\\Application\n{\n    /**\n     * Get the p"
  },
  {
    "path": "app/App/HomeController.php",
    "chars": 4412,
    "preview": "<?php\n\nnamespace BookStack\\App;\n\nuse BookStack\\Activity\\ActivityQueries;\nuse BookStack\\Entities\\Models\\Page;\nuse BookSta"
  },
  {
    "path": "app/App/MailNotification.php",
    "chars": 1107,
    "preview": "<?php\n\nnamespace BookStack\\App;\n\nuse BookStack\\Translation\\LocaleDefinition;\nuse BookStack\\Users\\Models\\User;\nuse Illumi"
  },
  {
    "path": "app/App/MetaController.php",
    "chars": 2087,
    "preview": "<?php\n\nnamespace BookStack\\App;\n\nuse BookStack\\Http\\Controller;\nuse BookStack\\Uploads\\FaviconHandler;\n\nclass MetaControl"
  },
  {
    "path": "app/App/Model.php",
    "chars": 446,
    "preview": "<?php\n\nnamespace BookStack\\App;\n\nuse Illuminate\\Database\\Eloquent\\Model as EloquentModel;\n\nclass Model extends EloquentM"
  },
  {
    "path": "app/App/Providers/AppServiceProvider.php",
    "chars": 2761,
    "preview": "<?php\n\nnamespace BookStack\\App\\Providers;\n\nuse BookStack\\Access\\SocialDriverManager;\nuse BookStack\\Activity\\Models\\Comme"
  },
  {
    "path": "app/App/Providers/AuthServiceProvider.php",
    "chars": 2361,
    "preview": "<?php\n\nnamespace BookStack\\App\\Providers;\n\nuse BookStack\\Access\\ExternalBaseUserProvider;\nuse BookStack\\Access\\Guards\\As"
  },
  {
    "path": "app/App/Providers/EventServiceProvider.php",
    "chars": 1456,
    "preview": "<?php\n\nnamespace BookStack\\App\\Providers;\n\nuse Illuminate\\Foundation\\Support\\Providers\\EventServiceProvider as ServicePr"
  },
  {
    "path": "app/App/Providers/RouteServiceProvider.php",
    "chars": 2739,
    "preview": "<?php\n\nnamespace BookStack\\App\\Providers;\n\nuse BookStack\\Facades\\Theme;\nuse BookStack\\Theming\\ThemeEvents;\nuse Illuminat"
  },
  {
    "path": "app/App/Providers/ThemeServiceProvider.php",
    "chars": 1743,
    "preview": "<?php\n\nnamespace BookStack\\App\\Providers;\n\nuse BookStack\\Theming\\ThemeEvents;\nuse BookStack\\Theming\\ThemeService;\nuse Bo"
  },
  {
    "path": "app/App/Providers/TranslationServiceProvider.php",
    "chars": 1579,
    "preview": "<?php\n\nnamespace BookStack\\App\\Providers;\n\nuse BookStack\\Translation\\FileLoader;\nuse BookStack\\Translation\\MessageSelect"
  },
  {
    "path": "app/App/Providers/ValidationRuleServiceProvider.php",
    "chars": 951,
    "preview": "<?php\n\nnamespace BookStack\\App\\Providers;\n\nuse BookStack\\Uploads\\ImageService;\nuse Illuminate\\Support\\Facades\\Validator;"
  },
  {
    "path": "app/App/Providers/ViewTweaksServiceProvider.php",
    "chars": 1163,
    "preview": "<?php\n\nnamespace BookStack\\App\\Providers;\n\nuse BookStack\\Entities\\BreadcrumbsViewComposer;\nuse BookStack\\Util\\DateFormat"
  },
  {
    "path": "app/App/PwaManifestBuilder.php",
    "chars": 2350,
    "preview": "<?php\n\nnamespace BookStack\\App;\n\nclass PwaManifestBuilder\n{\n    public function build(): array\n    {\n        // Note, wh"
  },
  {
    "path": "app/App/SluggableInterface.php",
    "chars": 181,
    "preview": "<?php\n\nnamespace BookStack\\App;\n\n/**\n * Assigned to models that can have slugs.\n * Must have the below properties.\n *\n *"
  },
  {
    "path": "app/App/SystemApiController.php",
    "chars": 828,
    "preview": "<?php\n\nnamespace BookStack\\App;\n\nuse BookStack\\Http\\ApiController;\nuse Illuminate\\Http\\JsonResponse;\n\nclass SystemApiCon"
  },
  {
    "path": "app/App/helpers.php",
    "chars": 2407,
    "preview": "<?php\n\nuse BookStack\\App\\AppVersion;\nuse BookStack\\App\\Model;\nuse BookStack\\Facades\\Theme;\nuse BookStack\\Permissions\\Per"
  },
  {
    "path": "app/Config/api.php",
    "chars": 839,
    "preview": "<?php\n\n/**\n * API configuration options.\n *\n * Changes to these config files are not supported by BookStack and may brea"
  },
  {
    "path": "app/Config/app.php",
    "chars": 7058,
    "preview": "<?php\n\n/**\n * Global app configuration options.\n *\n * Changes to these config files are not supported by BookStack and m"
  },
  {
    "path": "app/Config/auth.php",
    "chars": 3235,
    "preview": "<?php\n\n/**\n * Authentication configuration options.\n *\n * Changes to these config files are not supported by BookStack a"
  },
  {
    "path": "app/Config/cache.php",
    "chars": 2716,
    "preview": "<?php\n\nuse Illuminate\\Support\\Str;\n\n/**\n * Caching configuration options.\n *\n * Changes to these config files are not su"
  },
  {
    "path": "app/Config/clockwork.php",
    "chars": 15856,
    "preview": "<?php\n\nreturn [\n\n    /*\n    |-------------------------------------------------------------------------------------------"
  },
  {
    "path": "app/Config/database.php",
    "chars": 4229,
    "preview": "<?php\n\n/**\n * Database configuration options.\n *\n * Changes to these config files are not supported by BookStack and may"
  },
  {
    "path": "app/Config/debugbar.php",
    "chars": 6110,
    "preview": "<?php\n\n/**\n * Debugbar Configuration Options.\n *\n * Changes to these config files are not supported by BookStack and may"
  },
  {
    "path": "app/Config/exports.php",
    "chars": 12411,
    "preview": "<?php\n\n/**\n * Export configuration options.\n *\n * Changes to these config files are not supported by BookStack and may b"
  },
  {
    "path": "app/Config/filesystems.php",
    "chars": 2721,
    "preview": "<?php\n\n/**\n * Filesystem configuration options.\n *\n * Changes to these config files are not supported by BookStack and m"
  },
  {
    "path": "app/Config/hashing.php",
    "chars": 1278,
    "preview": "<?php\n\n/**\n * Hashing configuration options.\n *\n * Changes to these config files are not supported by BookStack and may "
  },
  {
    "path": "app/Config/logging.php",
    "chars": 4071,
    "preview": "<?php\n\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Handler\\ErrorLogHandler;\nuse Monolog\\Handler\\NullHandler;\nuse Mo"
  },
  {
    "path": "app/Config/mail.php",
    "chars": 2062,
    "preview": "<?php\n\n/**\n * Mail configuration options.\n *\n * Changes to these config files are not supported by BookStack and may bre"
  },
  {
    "path": "app/Config/oidc.php",
    "chars": 2938,
    "preview": "<?php\n\nreturn [\n\n    // Display name, shown to users, for OpenId option\n    'name' => env('OIDC_NAME', 'SSO'),\n\n    // D"
  },
  {
    "path": "app/Config/queue.php",
    "chars": 1406,
    "preview": "<?php\n\n/**\n * Queue configuration options.\n *\n * Changes to these config files are not supported by BookStack and may br"
  },
  {
    "path": "app/Config/saml2.php",
    "chars": 8343,
    "preview": "<?php\n\n$SAML2_IDP_AUTHNCONTEXT = env('SAML2_IDP_AUTHNCONTEXT', true);\n$SAML2_SP_x509 = env('SAML2_SP_x509', false);\n\nret"
  },
  {
    "path": "app/Config/services.php",
    "chars": 6239,
    "preview": "<?php\n\n/**\n * Third party service configuration options.\n *\n * Changes to these config files are not supported by BookSt"
  },
  {
    "path": "app/Config/session.php",
    "chars": 3854,
    "preview": "<?php\n\nuse Illuminate\\Support\\Str;\n\n/**\n * Session configuration options.\n *\n * Changes to these config files are not su"
  },
  {
    "path": "app/Config/setting-defaults.php",
    "chars": 1701,
    "preview": "<?php\n\n/**\n * Default system settings.\n *\n * Changes to these config files are not supported by BookStack and may break "
  },
  {
    "path": "app/Config/view.php",
    "chars": 1172,
    "preview": "<?php\n\n/**\n * View configuration options.\n *\n * Changes to these config files are not supported by BookStack and may bre"
  },
  {
    "path": "app/Console/Commands/AssignSortRuleCommand.php",
    "chars": 3268,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Entities\\Models\\Book;\nuse BookStack\\Sorting\\BookSorter;\nuse "
  },
  {
    "path": "app/Console/Commands/CleanupImagesCommand.php",
    "chars": 2242,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Uploads\\ImageService;\nuse Illuminate\\Console\\Command;\nuse Sy"
  },
  {
    "path": "app/Console/Commands/ClearActivityCommand.php",
    "chars": 687,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Activity\\Models\\Activity;\nuse Illuminate\\Console\\Command;\n\nc"
  },
  {
    "path": "app/Console/Commands/ClearRevisionsCommand.php",
    "chars": 892,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Entities\\Models\\PageRevision;\nuse Illuminate\\Console\\Command"
  },
  {
    "path": "app/Console/Commands/ClearViewsCommand.php",
    "chars": 654,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Activity\\Models\\View;\nuse Illuminate\\Console\\Command;\n\nclass"
  },
  {
    "path": "app/Console/Commands/CopyShelfPermissionsCommand.php",
    "chars": 2243,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Entities\\Queries\\BookshelfQueries;\nuse BookStack\\Entities\\To"
  },
  {
    "path": "app/Console/Commands/CreateAdminCommand.php",
    "chars": 5788,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Users\\Models\\Role;\nuse BookStack\\Users\\UserRepo;\nuse Illumin"
  },
  {
    "path": "app/Console/Commands/DeleteUsersCommand.php",
    "chars": 1355,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Users\\Models\\User;\nuse BookStack\\Users\\UserRepo;\nuse Illumin"
  },
  {
    "path": "app/Console/Commands/HandlesSingleUser.php",
    "chars": 974,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Users\\Models\\User;\nuse Exception;\nuse Illuminate\\Console\\Com"
  },
  {
    "path": "app/Console/Commands/InstallModuleCommand.php",
    "chars": 11080,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Http\\HttpRequestService;\nuse BookStack\\Theming\\ThemeModule;\n"
  },
  {
    "path": "app/Console/Commands/RefreshAvatarCommand.php",
    "chars": 3646,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Users\\Models\\User;\nuse Exception;\nuse Illuminate\\Console\\Com"
  },
  {
    "path": "app/Console/Commands/RegeneratePermissionsCommand.php",
    "chars": 1074,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Permissions\\JointPermissionBuilder;\nuse Illuminate\\Console\\C"
  },
  {
    "path": "app/Console/Commands/RegenerateReferencesCommand.php",
    "chars": 1067,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\References\\ReferenceStore;\nuse Illuminate\\Console\\Command;\nu"
  },
  {
    "path": "app/Console/Commands/RegenerateSearchCommand.php",
    "chars": 1269,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse BookStack\\Entities\\Models\\Entity;\nuse BookStack\\Search\\SearchIndex;\nus"
  },
  {
    "path": "app/Console/Commands/ResetMfaCommand.php",
    "chars": 1524,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse Exception;\nuse Illuminate\\Console\\Command;\n\nclass ResetMfaCommand exte"
  },
  {
    "path": "app/Console/Commands/UpdateUrlCommand.php",
    "chars": 4416,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Database\\Connection;\n\nclass"
  },
  {
    "path": "app/Console/Commands/UpgradeDatabaseEncodingCommand.php",
    "chars": 1399,
    "preview": "<?php\n\nnamespace BookStack\\Console\\Commands;\n\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Support\\Facades\\DB;\n\nclass "
  },
  {
    "path": "app/Console/Kernel.php",
    "chars": 603,
    "preview": "<?php\n\nnamespace BookStack\\Console;\n\nuse Illuminate\\Console\\Scheduling\\Schedule;\nuse Illuminate\\Foundation\\Console\\Kerne"
  },
  {
    "path": "app/Entities/BreadcrumbsViewComposer.php",
    "chars": 751,
    "preview": "<?php\n\nnamespace BookStack\\Entities;\n\nuse BookStack\\Entities\\Models\\Book;\nuse BookStack\\Entities\\Tools\\ShelfContext;\nuse"
  },
  {
    "path": "app/Entities/Controllers/BookApiController.php",
    "chars": 5955,
    "preview": "<?php\n\nnamespace BookStack\\Entities\\Controllers;\n\nuse BookStack\\Api\\ApiEntityListFormatter;\nuse BookStack\\Entities\\Model"
  },
  {
    "path": "app/Entities/Controllers/BookController.php",
    "chars": 10049,
    "preview": "<?php\n\nnamespace BookStack\\Entities\\Controllers;\n\nuse BookStack\\Activity\\ActivityQueries;\nuse BookStack\\Activity\\Activit"
  },
  {
    "path": "app/Entities/Controllers/BookshelfApiController.php",
    "chars": 5149,
    "preview": "<?php\n\nnamespace BookStack\\Entities\\Controllers;\n\nuse BookStack\\Entities\\Models\\Bookshelf;\nuse BookStack\\Entities\\Querie"
  },
  {
    "path": "app/Entities/Controllers/BookshelfController.php",
    "chars": 8215,
    "preview": "<?php\n\nnamespace BookStack\\Entities\\Controllers;\n\nuse BookStack\\Activity\\ActivityQueries;\nuse BookStack\\Activity\\Models\\"
  },
  {
    "path": "app/Entities/Controllers/ChapterApiController.php",
    "chars": 5387,
    "preview": "<?php\n\nnamespace BookStack\\Entities\\Controllers;\n\nuse BookStack\\Entities\\Models\\Book;\nuse BookStack\\Entities\\Models\\Chap"
  },
  {
    "path": "app/Entities/Controllers/ChapterController.php",
    "chars": 10242,
    "preview": "<?php\n\nnamespace BookStack\\Entities\\Controllers;\n\nuse BookStack\\Activity\\Models\\View;\nuse BookStack\\Activity\\Tools\\UserE"
  },
  {
    "path": "app/Entities/Controllers/PageApiController.php",
    "chars": 6317,
    "preview": "<?php\n\nnamespace BookStack\\Entities\\Controllers;\n\nuse BookStack\\Activity\\Tools\\CommentTree;\nuse BookStack\\Entities\\Queri"
  },
  {
    "path": "app/Entities/Controllers/PageController.php",
    "chars": 16194,
    "preview": "<?php\n\nnamespace BookStack\\Entities\\Controllers;\n\nuse BookStack\\Activity\\Models\\View;\nuse BookStack\\Activity\\Tools\\Comme"
  },
  {
    "path": "app/Entities/Controllers/PageRevisionController.php",
    "chars": 6218,
    "preview": "<?php\n\nnamespace BookStack\\Entities\\Controllers;\n\nuse BookStack\\Activity\\ActivityType;\nuse BookStack\\Entities\\Models\\Pag"
  },
  {
    "path": "app/Entities/Controllers/PageTemplateController.php",
    "chars": 1662,
    "preview": "<?php\n\nnamespace BookStack\\Entities\\Controllers;\n\nuse BookStack\\Entities\\Queries\\PageQueries;\nuse BookStack\\Entities\\Rep"
  },
  {
    "path": "app/Entities/Controllers/RecycleBinApiController.php",
    "chars": 3321,
    "preview": "<?php\n\nnamespace BookStack\\Entities\\Controllers;\n\nuse BookStack\\Entities\\Models\\Book;\nuse BookStack\\Entities\\Models\\Book"
  }
]

// ... and 2070 more files (download for full content)

About this extraction

This page contains the full source code of the BookStackApp/BookStack GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 2270 files (11.4 MB), approximately 3.1M tokens, and a symbol index with 8480 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!