]> BookStack Code Mirror - bookstack/commitdiff
Merge branch 'feature/saml' of git://github.com/Xiphoseer/BookStack into Xiphoseer...
authorDan Brown <redacted>
Sat, 16 Nov 2019 12:42:45 +0000 (12:42 +0000)
committerDan Brown <redacted>
Sat, 16 Nov 2019 12:42:45 +0000 (12:42 +0000)
613 files changed:
.env.example.complete
.github/workflows/phpunit.yml [new file with mode: 0644]
.gitignore
.travis.yml [deleted file]
app/Actions/Activity.php
app/Actions/ActivityService.php
app/Actions/ViewService.php
app/Application.php
app/Auth/Access/EmailConfirmationService.php
app/Auth/Access/SocialAuthService.php
app/Auth/Access/UserInviteService.php [new file with mode: 0644]
app/Auth/Access/UserTokenService.php [new file with mode: 0644]
app/Auth/Permissions/PermissionService.php
app/Auth/Permissions/PermissionsRepo.php
app/Auth/Role.php
app/Auth/User.php
app/Auth/UserRepo.php
app/Config/app.php
app/Config/auth.php
app/Config/broadcasting.php
app/Config/cache.php
app/Config/database.php
app/Config/debugbar.php
app/Config/dompdf.php
app/Config/hashing.php [new file with mode: 0644]
app/Config/logging.php [new file with mode: 0644]
app/Config/mail.php
app/Config/queue.php
app/Config/services.php
app/Config/session.php
app/Config/setting-defaults.php
app/Entities/Book.php
app/Entities/BookChild.php [new file with mode: 0644]
app/Entities/Bookshelf.php
app/Entities/BreadcrumbsViewComposer.php
app/Entities/Chapter.php
app/Entities/Entity.php
app/Entities/EntityProvider.php
app/Entities/ExportService.php
app/Entities/HasCoverImage.php [new file with mode: 0644]
app/Entities/Managers/BookContents.php [new file with mode: 0644]
app/Entities/Managers/EntityContext.php [moved from app/Entities/EntityContextManager.php with 53% similarity]
app/Entities/Managers/PageContent.php [new file with mode: 0644]
app/Entities/Managers/PageEditActivity.php [new file with mode: 0644]
app/Entities/Managers/TrashCan.php [new file with mode: 0644]
app/Entities/Page.php
app/Entities/PageRevision.php
app/Entities/Repos/BaseRepo.php [new file with mode: 0644]
app/Entities/Repos/BookRepo.php [new file with mode: 0644]
app/Entities/Repos/BookshelfRepo.php [new file with mode: 0644]
app/Entities/Repos/ChapterRepo.php [new file with mode: 0644]
app/Entities/Repos/EntityRepo.php [deleted file]
app/Entities/Repos/PageRepo.php
app/Entities/SearchService.php
app/Entities/SlugGenerator.php [new file with mode: 0644]
app/Exceptions/MoveOperationException.php [new file with mode: 0644]
app/Exceptions/SortOperationException.php [new file with mode: 0644]
app/Exceptions/UserTokenExpiredException.php [new file with mode: 0644]
app/Exceptions/UserTokenNotFoundException.php [new file with mode: 0644]
app/Facades/Permissions.php [new file with mode: 0644]
app/Http/Controllers/AttachmentController.php
app/Http/Controllers/Auth/ConfirmEmailController.php [new file with mode: 0644]
app/Http/Controllers/Auth/ForgotPasswordController.php
app/Http/Controllers/Auth/LoginController.php
app/Http/Controllers/Auth/RegisterController.php
app/Http/Controllers/Auth/ResetPasswordController.php
app/Http/Controllers/Auth/UserInviteController.php [new file with mode: 0644]
app/Http/Controllers/BookController.php
app/Http/Controllers/BookExportController.php [new file with mode: 0644]
app/Http/Controllers/BookSortController.php [new file with mode: 0644]
app/Http/Controllers/BookshelfController.php
app/Http/Controllers/ChapterController.php
app/Http/Controllers/ChapterExportController.php [new file with mode: 0644]
app/Http/Controllers/CommentController.php
app/Http/Controllers/Controller.php
app/Http/Controllers/HomeController.php
app/Http/Controllers/Images/ImageController.php
app/Http/Controllers/PageController.php
app/Http/Controllers/PageExportController.php [new file with mode: 0644]
app/Http/Controllers/PageRevisionController.php [new file with mode: 0644]
app/Http/Controllers/PageTemplateController.php [new file with mode: 0644]
app/Http/Controllers/PermissionController.php
app/Http/Controllers/SearchController.php
app/Http/Controllers/SettingController.php
app/Http/Controllers/UserController.php
app/Http/Kernel.php
app/Http/Middleware/CheckForMaintenanceMode.php [new file with mode: 0644]
app/Http/Middleware/GlobalViewData.php [new file with mode: 0644]
app/Http/Middleware/Localization.php
app/Http/Middleware/TrustProxies.php
app/Http/Middleware/VerifyCsrfToken.php
app/Http/Request.php
app/Notifications/TestEmail.php [new file with mode: 0644]
app/Notifications/UserInvite.php [new file with mode: 0644]
app/Providers/AppServiceProvider.php
app/Providers/CustomFacadeProvider.php
app/Providers/TranslationServiceProvider.php
app/Translation/FileLoader.php [new file with mode: 0644]
app/Translation/Translator.php [deleted file]
app/Uploads/Attachment.php
app/Uploads/AttachmentService.php
app/Uploads/ImageService.php
app/helpers.php
bootstrap/app.php
composer.json
composer.lock
crowdin.yml [new file with mode: 0644]
database/factories/ModelFactory.php
database/migrations/2019_07_07_112515_add_template_support.php [new file with mode: 0644]
database/migrations/2019_08_17_140214_add_user_invites_table.php [new file with mode: 0644]
database/seeds/DatabaseSeeder.php
database/seeds/DummyContentSeeder.php
database/seeds/LargeContentSeeder.php
dev/docker/Dockerfile [new file with mode: 0644]
dev/docker/entrypoint.app.sh [new file with mode: 0755]
dev/docker/entrypoint.node.sh [new file with mode: 0755]
docker-compose.yml [new file with mode: 0644]
package-lock.json
phpunit.xml
public/.htaccess
public/uploads/.gitignore
public/uploads/.htaccess [new file with mode: 0755]
public/web.config [new file with mode: 0644]
readme.md
resources/assets/js/components/dropdown.js [deleted file]
resources/assets/js/services/events.js [deleted file]
resources/assets/sass/print-styles.scss [deleted file]
resources/icons/add-circle.svg [moved from resources/assets/icons/add-circle.svg with 100% similarity]
resources/icons/add.svg [moved from resources/assets/icons/add.svg with 100% similarity]
resources/icons/attach.svg [moved from resources/assets/icons/attach.svg with 100% similarity]
resources/icons/auth/azure.svg [moved from resources/assets/icons/auth/azure.svg with 100% similarity]
resources/icons/auth/discord.svg [moved from resources/assets/icons/auth/discord.svg with 100% similarity]
resources/icons/auth/facebook.svg [moved from resources/assets/icons/auth/facebook.svg with 100% similarity]
resources/icons/auth/github.svg [moved from resources/assets/icons/auth/github.svg with 100% similarity]
resources/icons/auth/gitlab.svg [moved from resources/assets/icons/auth/gitlab.svg with 100% similarity]
resources/icons/auth/google.svg [moved from resources/assets/icons/auth/google.svg with 100% similarity]
resources/icons/auth/okta.svg [moved from resources/assets/icons/auth/okta.svg with 100% similarity]
resources/icons/auth/slack.svg [moved from resources/assets/icons/auth/slack.svg with 100% similarity]
resources/icons/auth/twitch.svg [moved from resources/assets/icons/auth/twitch.svg with 100% similarity]
resources/icons/auth/twitter.svg [moved from resources/assets/icons/auth/twitter.svg with 100% similarity]
resources/icons/back.svg [moved from resources/assets/icons/back.svg with 100% similarity]
resources/icons/book.svg [moved from resources/assets/icons/book.svg with 100% similarity]
resources/icons/books.svg [moved from resources/assets/icons/books.svg with 100% similarity]
resources/icons/bookshelf.svg [moved from resources/assets/icons/bookshelf.svg with 100% similarity]
resources/icons/cancel.svg [moved from resources/assets/icons/cancel.svg with 100% similarity]
resources/icons/caret-down.svg [moved from resources/assets/icons/caret-down.svg with 100% similarity]
resources/icons/caret-left-circle.svg [moved from resources/assets/icons/caret-left-circle.svg with 100% similarity]
resources/icons/caret-right-circle.svg [moved from resources/assets/icons/caret-right-circle.svg with 100% similarity]
resources/icons/caret-right.svg [moved from resources/assets/icons/caret-right.svg with 100% similarity]
resources/icons/chapter.svg [moved from resources/assets/icons/chapter.svg with 100% similarity]
resources/icons/check-circle.svg [moved from resources/assets/icons/check-circle.svg with 100% similarity]
resources/icons/check.svg [moved from resources/assets/icons/check.svg with 100% similarity]
resources/icons/chevron-down.svg [new file with mode: 0644]
resources/icons/chevron-right.svg [moved from resources/assets/icons/chevron-right.svg with 100% similarity]
resources/icons/chevron-up.svg [moved from resources/assets/icons/chevron-up.svg with 100% similarity]
resources/icons/close.svg [moved from resources/assets/icons/close.svg with 100% similarity]
resources/icons/comment.svg [moved from resources/assets/icons/comment.svg with 100% similarity]
resources/icons/copy.svg [moved from resources/assets/icons/copy.svg with 100% similarity]
resources/icons/danger.svg [moved from resources/assets/icons/danger.svg with 100% similarity]
resources/icons/delete.svg [moved from resources/assets/icons/delete.svg with 100% similarity]
resources/icons/drawing.svg [moved from resources/assets/icons/drawing.svg with 100% similarity]
resources/icons/edit.svg [moved from resources/assets/icons/edit.svg with 100% similarity]
resources/icons/expand-text.svg [moved from resources/assets/icons/expand-text.svg with 100% similarity]
resources/icons/export.svg [moved from resources/assets/icons/export.svg with 100% similarity]
resources/icons/file.svg [moved from resources/assets/icons/file.svg with 100% similarity]
resources/icons/folder.svg [moved from resources/assets/icons/folder.svg with 100% similarity]
resources/icons/grid.svg [moved from resources/assets/icons/grid.svg with 100% similarity]
resources/icons/grip.svg [moved from resources/assets/icons/grip.svg with 100% similarity]
resources/icons/history.svg [moved from resources/assets/icons/history.svg with 100% similarity]
resources/icons/image.svg [moved from resources/assets/icons/image.svg with 100% similarity]
resources/icons/images.svg [moved from resources/assets/icons/images.svg with 100% similarity]
resources/icons/include.svg [moved from resources/assets/icons/include.svg with 100% similarity]
resources/icons/info-filled.svg [moved from resources/assets/icons/info-filled.svg with 100% similarity]
resources/icons/info.svg [moved from resources/assets/icons/info.svg with 100% similarity]
resources/icons/link.svg [moved from resources/assets/icons/link.svg with 100% similarity]
resources/icons/list.svg [moved from resources/assets/icons/list.svg with 100% similarity]
resources/icons/lock-open.svg [moved from resources/assets/icons/lock-open.svg with 100% similarity]
resources/icons/lock.svg [moved from resources/assets/icons/lock.svg with 100% similarity]
resources/icons/login.svg [moved from resources/assets/icons/login.svg with 100% similarity]
resources/icons/logout.svg [moved from resources/assets/icons/logout.svg with 100% similarity]
resources/icons/more.svg [moved from resources/assets/icons/more.svg with 100% similarity]
resources/icons/new-user.svg [moved from resources/assets/icons/new-user.svg with 100% similarity]
resources/icons/open-book.svg [moved from resources/assets/icons/open-book.svg with 100% similarity]
resources/icons/page.svg [moved from resources/assets/icons/page.svg with 100% similarity]
resources/icons/permission.svg [moved from resources/assets/icons/permission.svg with 100% similarity]
resources/icons/popular.svg [moved from resources/assets/icons/popular.svg with 100% similarity]
resources/icons/reply.svg [moved from resources/assets/icons/reply.svg with 100% similarity]
resources/icons/save.svg [moved from resources/assets/icons/save.svg with 100% similarity]
resources/icons/search.svg [moved from resources/assets/icons/search.svg with 100% similarity]
resources/icons/settings.svg [moved from resources/assets/icons/settings.svg with 100% similarity]
resources/icons/sort-down.svg [moved from resources/assets/icons/sort-down.svg with 100% similarity]
resources/icons/sort-up.svg [moved from resources/assets/icons/sort-up.svg with 100% similarity]
resources/icons/sort.svg [moved from resources/assets/icons/sort.svg with 100% similarity]
resources/icons/spanner.svg [moved from resources/assets/icons/spanner.svg with 100% similarity]
resources/icons/star-circle.svg [moved from resources/assets/icons/star-circle.svg with 100% similarity]
resources/icons/star.svg [moved from resources/assets/icons/star.svg with 100% similarity]
resources/icons/swap-vertical.svg [moved from resources/assets/icons/swap-vertical.svg with 100% similarity]
resources/icons/tag.svg [moved from resources/assets/icons/tag.svg with 100% similarity]
resources/icons/template.svg [new file with mode: 0644]
resources/icons/time.svg [moved from resources/assets/icons/time.svg with 100% similarity]
resources/icons/user.svg [moved from resources/assets/icons/user.svg with 100% similarity]
resources/icons/users-add.svg [moved from resources/assets/icons/users-add.svg with 100% similarity]
resources/icons/users.svg [moved from resources/assets/icons/users.svg with 100% similarity]
resources/icons/view.svg [moved from resources/assets/icons/view.svg with 100% similarity]
resources/icons/warning.svg [moved from resources/assets/icons/warning.svg with 100% similarity]
resources/js/components/back-to-top.js [moved from resources/assets/js/components/back-to-top.js with 100% similarity]
resources/js/components/book-sort.js [moved from resources/assets/js/components/book-sort.js with 100% similarity]
resources/js/components/breadcrumb-listing.js [moved from resources/assets/js/components/breadcrumb-listing.js with 63% similarity]
resources/js/components/chapter-toggle.js [moved from resources/assets/js/components/chapter-toggle.js with 86% similarity]
resources/js/components/collapsible.js [moved from resources/assets/js/components/collapsible.js with 84% similarity]
resources/js/components/custom-checkbox.js [moved from resources/assets/js/components/custom-checkbox.js with 100% similarity]
resources/js/components/dropdown.js [new file with mode: 0644]
resources/js/components/editor-toolbox.js [moved from resources/assets/js/components/editor-toolbox.js with 90% similarity]
resources/js/components/entity-permissions-editor.js [moved from resources/assets/js/components/entity-permissions-editor.js with 100% similarity]
resources/js/components/entity-selector-popup.js [moved from resources/assets/js/components/entity-selector-popup.js with 100% similarity]
resources/js/components/entity-selector.js [moved from resources/assets/js/components/entity-selector.js with 100% similarity]
resources/js/components/expand-toggle.js [moved from resources/assets/js/components/expand-toggle.js with 100% similarity]
resources/js/components/header-mobile-toggle.js [moved from resources/assets/js/components/header-mobile-toggle.js with 100% similarity]
resources/js/components/homepage-control.js [moved from resources/assets/js/components/homepage-control.js with 100% similarity]
resources/js/components/image-picker.js [moved from resources/assets/js/components/image-picker.js with 100% similarity]
resources/js/components/index.js [moved from resources/assets/js/components/index.js with 93% similarity]
resources/js/components/list-sort-control.js [moved from resources/assets/js/components/list-sort-control.js with 100% similarity]
resources/js/components/markdown-editor.js [moved from resources/assets/js/components/markdown-editor.js with 82% similarity]
resources/js/components/new-user-password.js [new file with mode: 0644]
resources/js/components/notification.js [moved from resources/assets/js/components/notification.js with 100% similarity]
resources/js/components/overlay.js [moved from resources/assets/js/components/overlay.js with 73% similarity]
resources/js/components/page-comments.js [moved from resources/assets/js/components/page-comments.js with 99% similarity]
resources/js/components/page-display.js [moved from resources/assets/js/components/page-display.js with 100% similarity]
resources/js/components/page-picker.js [moved from resources/assets/js/components/page-picker.js with 100% similarity]
resources/js/components/permissions-table.js [moved from resources/assets/js/components/permissions-table.js with 100% similarity]
resources/js/components/setting-app-color-picker.js [moved from resources/assets/js/components/setting-app-color-picker.js with 95% similarity]
resources/js/components/shelf-sort.js [moved from resources/assets/js/components/shelf-sort.js with 100% similarity]
resources/js/components/sidebar.js [moved from resources/assets/js/components/sidebar.js with 100% similarity]
resources/js/components/template-manager.js [new file with mode: 0644]
resources/js/components/toggle-switch.js [moved from resources/assets/js/components/toggle-switch.js with 62% similarity]
resources/js/components/tri-layout.js [moved from resources/assets/js/components/tri-layout.js with 100% similarity]
resources/js/components/wysiwyg-editor.js [moved from resources/assets/js/components/wysiwyg-editor.js with 93% similarity]
resources/js/index.js [moved from resources/assets/js/index.js with 100% similarity]
resources/js/services/animations.js [moved from resources/assets/js/services/animations.js with 79% similarity]
resources/js/services/code.js [moved from resources/assets/js/services/code.js with 93% similarity]
resources/js/services/dates.js [moved from resources/assets/js/services/dates.js with 100% similarity]
resources/js/services/dom.js [moved from resources/assets/js/services/dom.js with 78% similarity]
resources/js/services/drawio.js [moved from resources/assets/js/services/drawio.js with 100% similarity]
resources/js/services/events.js [new file with mode: 0644]
resources/js/services/http.js [moved from resources/assets/js/services/http.js with 98% similarity]
resources/js/services/translations.js [moved from resources/assets/js/services/translations.js with 95% similarity]
resources/js/services/util.js [moved from resources/assets/js/services/util.js with 100% similarity]
resources/js/vues/attachment-manager.js [moved from resources/assets/js/vues/attachment-manager.js with 100% similarity]
resources/js/vues/code-editor.js [moved from resources/assets/js/vues/code-editor.js with 89% similarity]
resources/js/vues/components/autosuggest.js [moved from resources/assets/js/vues/components/autosuggest.js with 90% similarity]
resources/js/vues/components/dropzone.js [moved from resources/assets/js/vues/components/dropzone.js with 94% similarity]
resources/js/vues/entity-dashboard.js [moved from resources/assets/js/vues/entity-dashboard.js with 100% similarity]
resources/js/vues/image-manager.js [moved from resources/assets/js/vues/image-manager.js with 100% similarity]
resources/js/vues/page-editor.js [moved from resources/assets/js/vues/page-editor.js with 100% similarity]
resources/js/vues/search.js [moved from resources/assets/js/vues/search.js with 100% similarity]
resources/js/vues/tag-manager.js [moved from resources/assets/js/vues/tag-manager.js with 98% similarity]
resources/js/vues/vues.js [moved from resources/assets/js/vues/vues.js with 100% similarity]
resources/lang/ar/activities.php
resources/lang/ar/auth.php
resources/lang/ar/common.php
resources/lang/ar/components.php
resources/lang/ar/entities.php
resources/lang/ar/errors.php
resources/lang/ar/pagination.php
resources/lang/ar/passwords.php
resources/lang/ar/settings.php
resources/lang/ar/validation.php
resources/lang/check.php [deleted file]
resources/lang/cs/auth.php
resources/lang/cs/common.php
resources/lang/cs/entities.php
resources/lang/cs/errors.php
resources/lang/cs/pagination.php
resources/lang/cs/settings.php
resources/lang/cs/validation.php
resources/lang/de/activities.php
resources/lang/de/auth.php
resources/lang/de/common.php
resources/lang/de/components.php
resources/lang/de/entities.php
resources/lang/de/errors.php
resources/lang/de/pagination.php
resources/lang/de/passwords.php
resources/lang/de/settings.php
resources/lang/de/validation.php
resources/lang/de_informal/activities.php
resources/lang/de_informal/auth.php
resources/lang/de_informal/common.php
resources/lang/de_informal/components.php
resources/lang/de_informal/entities.php
resources/lang/de_informal/errors.php
resources/lang/de_informal/pagination.php
resources/lang/de_informal/passwords.php
resources/lang/de_informal/settings.php
resources/lang/de_informal/validation.php
resources/lang/en/auth.php
resources/lang/en/common.php
resources/lang/en/entities.php
resources/lang/en/errors.php
resources/lang/en/passwords.php
resources/lang/en/settings.php
resources/lang/en/validation.php
resources/lang/es/auth.php
resources/lang/es/common.php
resources/lang/es/entities.php
resources/lang/es/errors.php
resources/lang/es/settings.php
resources/lang/es/validation.php
resources/lang/es_AR/activities.php
resources/lang/es_AR/auth.php
resources/lang/es_AR/common.php
resources/lang/es_AR/components.php
resources/lang/es_AR/entities.php
resources/lang/es_AR/errors.php
resources/lang/es_AR/pagination.php
resources/lang/es_AR/passwords.php
resources/lang/es_AR/settings.php
resources/lang/es_AR/validation.php
resources/lang/format.php [deleted file]
resources/lang/fr/activities.php
resources/lang/fr/auth.php
resources/lang/fr/common.php
resources/lang/fr/components.php
resources/lang/fr/entities.php
resources/lang/fr/errors.php
resources/lang/fr/pagination.php
resources/lang/fr/passwords.php
resources/lang/fr/settings.php
resources/lang/fr/validation.php
resources/lang/hu/auth.php
resources/lang/hu/common.php
resources/lang/hu/entities.php
resources/lang/hu/errors.php
resources/lang/hu/settings.php
resources/lang/hu/validation.php
resources/lang/it/activities.php
resources/lang/it/auth.php
resources/lang/it/common.php
resources/lang/it/components.php
resources/lang/it/entities.php
resources/lang/it/errors.php
resources/lang/it/pagination.php
resources/lang/it/passwords.php
resources/lang/it/settings.php
resources/lang/it/validation.php
resources/lang/ja/activities.php
resources/lang/ja/auth.php
resources/lang/ja/common.php
resources/lang/ja/components.php
resources/lang/ja/entities.php
resources/lang/ja/errors.php
resources/lang/ja/pagination.php
resources/lang/ja/passwords.php
resources/lang/ja/settings.php
resources/lang/ja/validation.php
resources/lang/ko/activities.php [moved from resources/lang/kr/activities.php with 72% similarity]
resources/lang/ko/auth.php [moved from resources/lang/kr/auth.php with 75% similarity]
resources/lang/ko/common.php [moved from resources/lang/kr/common.php with 68% similarity]
resources/lang/ko/components.php [moved from resources/lang/kr/components.php with 93% similarity]
resources/lang/ko/entities.php [moved from resources/lang/kr/entities.php with 90% similarity]
resources/lang/ko/errors.php [moved from resources/lang/kr/errors.php with 94% similarity]
resources/lang/ko/pagination.php [new file with mode: 0644]
resources/lang/ko/passwords.php [new file with mode: 0644]
resources/lang/ko/settings.php [moved from resources/lang/kr/settings.php with 68% similarity]
resources/lang/ko/validation.php [moved from resources/lang/kr/validation.php with 66% similarity]
resources/lang/kr/pagination.php [deleted file]
resources/lang/kr/passwords.php [deleted file]
resources/lang/nl/activities.php
resources/lang/nl/auth.php
resources/lang/nl/common.php
resources/lang/nl/components.php
resources/lang/nl/entities.php
resources/lang/nl/errors.php
resources/lang/nl/pagination.php
resources/lang/nl/passwords.php
resources/lang/nl/settings.php
resources/lang/nl/validation.php
resources/lang/pl/activities.php
resources/lang/pl/auth.php
resources/lang/pl/common.php
resources/lang/pl/components.php
resources/lang/pl/entities.php
resources/lang/pl/errors.php
resources/lang/pl/pagination.php
resources/lang/pl/passwords.php
resources/lang/pl/settings.php
resources/lang/pl/validation.php
resources/lang/pt_BR/activities.php
resources/lang/pt_BR/auth.php
resources/lang/pt_BR/common.php
resources/lang/pt_BR/components.php
resources/lang/pt_BR/entities.php
resources/lang/pt_BR/errors.php
resources/lang/pt_BR/settings.php
resources/lang/pt_BR/validation.php
resources/lang/ru/activities.php
resources/lang/ru/auth.php
resources/lang/ru/common.php
resources/lang/ru/components.php
resources/lang/ru/entities.php
resources/lang/ru/errors.php
resources/lang/ru/pagination.php
resources/lang/ru/passwords.php
resources/lang/ru/settings.php
resources/lang/ru/validation.php
resources/lang/sk/activities.php
resources/lang/sk/auth.php
resources/lang/sk/common.php
resources/lang/sk/components.php
resources/lang/sk/entities.php
resources/lang/sk/errors.php
resources/lang/sk/pagination.php
resources/lang/sk/passwords.php
resources/lang/sk/settings.php
resources/lang/sk/validation.php
resources/lang/sv/activities.php
resources/lang/sv/auth.php
resources/lang/sv/common.php
resources/lang/sv/components.php
resources/lang/sv/entities.php
resources/lang/sv/errors.php
resources/lang/sv/pagination.php
resources/lang/sv/passwords.php
resources/lang/sv/settings.php
resources/lang/sv/validation.php
resources/lang/tr/activities.php [new file with mode: 0644]
resources/lang/tr/auth.php [new file with mode: 0644]
resources/lang/tr/common.php [new file with mode: 0644]
resources/lang/tr/components.php [new file with mode: 0644]
resources/lang/tr/entities.php [new file with mode: 0644]
resources/lang/tr/errors.php [new file with mode: 0644]
resources/lang/tr/pagination.php [new file with mode: 0644]
resources/lang/tr/passwords.php [new file with mode: 0644]
resources/lang/tr/settings.php [new file with mode: 0755]
resources/lang/tr/validation.php [new file with mode: 0644]
resources/lang/uk/activities.php
resources/lang/uk/auth.php
resources/lang/uk/common.php
resources/lang/uk/components.php
resources/lang/uk/entities.php
resources/lang/uk/errors.php
resources/lang/uk/pagination.php
resources/lang/uk/passwords.php
resources/lang/uk/settings.php
resources/lang/uk/validation.php
resources/lang/zh_CN/activities.php
resources/lang/zh_CN/auth.php
resources/lang/zh_CN/common.php
resources/lang/zh_CN/components.php
resources/lang/zh_CN/entities.php
resources/lang/zh_CN/errors.php
resources/lang/zh_CN/pagination.php
resources/lang/zh_CN/passwords.php
resources/lang/zh_CN/settings.php
resources/lang/zh_CN/validation.php
resources/lang/zh_TW/activities.php
resources/lang/zh_TW/auth.php
resources/lang/zh_TW/common.php
resources/lang/zh_TW/components.php
resources/lang/zh_TW/entities.php
resources/lang/zh_TW/errors.php
resources/lang/zh_TW/pagination.php
resources/lang/zh_TW/passwords.php
resources/lang/zh_TW/settings.php
resources/lang/zh_TW/validation.php
resources/sass/_animations.scss [moved from resources/assets/sass/_animations.scss with 100% similarity]
resources/sass/_blocks.scss [moved from resources/assets/sass/_blocks.scss with 98% similarity]
resources/sass/_buttons.scss [moved from resources/assets/sass/_buttons.scss with 60% similarity]
resources/sass/_codemirror.scss [moved from resources/assets/sass/_codemirror.scss with 100% similarity]
resources/sass/_colors.scss [moved from resources/assets/sass/_colors.scss with 52% similarity]
resources/sass/_components.scss [moved from resources/assets/sass/_components.scss with 92% similarity]
resources/sass/_forms.scss [moved from resources/assets/sass/_forms.scss with 90% similarity]
resources/sass/_header.scss [moved from resources/assets/sass/_header.scss with 93% similarity]
resources/sass/_html.scss [moved from resources/assets/sass/_html.scss with 81% similarity]
resources/sass/_layout.scss [moved from resources/assets/sass/_layout.scss with 97% similarity]
resources/sass/_lists.scss [moved from resources/assets/sass/_lists.scss with 93% similarity]
resources/sass/_mixins.scss [moved from resources/assets/sass/_mixins.scss with 100% similarity]
resources/sass/_pages.scss [moved from resources/assets/sass/_pages.scss with 98% similarity]
resources/sass/_reset.scss [moved from resources/assets/sass/_reset.scss with 100% similarity]
resources/sass/_spacing.scss [moved from resources/assets/sass/_spacing.scss with 100% similarity]
resources/sass/_tables.scss [moved from resources/assets/sass/_tables.scss with 100% similarity]
resources/sass/_text.scss [moved from resources/assets/sass/_text.scss with 94% similarity]
resources/sass/_tinymce.scss [moved from resources/assets/sass/_tinymce.scss with 98% similarity]
resources/sass/_variables.scss [moved from resources/assets/sass/_variables.scss with 79% similarity]
resources/sass/export-styles.scss [moved from resources/assets/sass/export-styles.scss with 97% similarity]
resources/sass/print-styles.scss [new file with mode: 0644]
resources/sass/styles.scss [moved from resources/assets/sass/styles.scss with 93% similarity]
resources/views/auth/forms/login/ldap.blade.php
resources/views/auth/forms/login/standard.blade.php
resources/views/auth/invite-set-password.blade.php [new file with mode: 0644]
resources/views/auth/login.blade.php
resources/views/auth/passwords/email.blade.php
resources/views/auth/passwords/reset.blade.php
resources/views/auth/register.blade.php
resources/views/auth/user-unconfirmed.blade.php
resources/views/base.blade.php
resources/views/books/create.blade.php
resources/views/books/delete.blade.php
resources/views/books/edit.blade.php
resources/views/books/export.blade.php
resources/views/books/form.blade.php
resources/views/books/list.blade.php
resources/views/books/permissions.blade.php
resources/views/books/show.blade.php
resources/views/books/sort.blade.php
resources/views/chapters/child-menu.blade.php
resources/views/chapters/create.blade.php
resources/views/chapters/delete.blade.php
resources/views/chapters/edit.blade.php
resources/views/chapters/export.blade.php
resources/views/chapters/form.blade.php
resources/views/chapters/list-item.blade.php
resources/views/chapters/move.blade.php
resources/views/chapters/permissions.blade.php
resources/views/chapters/show.blade.php
resources/views/comments/comment.blade.php
resources/views/comments/comments.blade.php
resources/views/comments/create.blade.php
resources/views/common/header.blade.php
resources/views/common/home-custom.blade.php
resources/views/components/code-editor.blade.php
resources/views/components/custom-checkbox.blade.php
resources/views/components/entity-selector-popup.blade.php
resources/views/components/expand-toggle.blade.php
resources/views/components/image-manager.blade.php
resources/views/components/image-picker.blade.php
resources/views/components/tag-manager.blade.php
resources/views/components/toggle-switch.blade.php
resources/views/errors/404.blade.php
resources/views/form/entity-permissions.blade.php
resources/views/form/password.blade.php
resources/views/form/text.blade.php
resources/views/pages/attachment-manager.blade.php [new file with mode: 0644]
resources/views/pages/copy.blade.php
resources/views/pages/delete.blade.php
resources/views/pages/detailed-listing.blade.php
resources/views/pages/edit.blade.php
resources/views/pages/editor-toolbox.blade.php [new file with mode: 0644]
resources/views/pages/export.blade.php
resources/views/pages/form-toolbox.blade.php [deleted file]
resources/views/pages/form.blade.php
resources/views/pages/guest-create.blade.php
resources/views/pages/markdown-editor.blade.php
resources/views/pages/move.blade.php
resources/views/pages/permissions.blade.php
resources/views/pages/pointer.blade.php
resources/views/pages/revision.blade.php
resources/views/pages/revisions.blade.php
resources/views/pages/show.blade.php
resources/views/pages/template-manager-list.blade.php [new file with mode: 0644]
resources/views/pages/template-manager.blade.php [new file with mode: 0644]
resources/views/pages/wysiwyg-editor.blade.php
resources/views/partials/book-tree.blade.php
resources/views/partials/breadcrumb-listing.blade.php
resources/views/partials/breadcrumbs.blade.php
resources/views/partials/custom-styles.blade.php
resources/views/partials/entity-dashboard-search-box.blade.php
resources/views/partials/entity-export-menu.blade.php [new file with mode: 0644]
resources/views/partials/entity-list-item-basic.blade.php
resources/views/partials/notifications.blade.php
resources/views/partials/sort.blade.php
resources/views/search/all.blade.php
resources/views/settings/index.blade.php
resources/views/settings/maintenance.blade.php
resources/views/settings/navbar.blade.php
resources/views/settings/roles/delete.blade.php
resources/views/settings/roles/form.blade.php
resources/views/shelves/create.blade.php
resources/views/shelves/edit.blade.php
resources/views/shelves/form.blade.php
resources/views/shelves/list.blade.php
resources/views/shelves/show.blade.php
resources/views/tri-layout.blade.php
resources/views/users/create.blade.php
resources/views/users/delete.blade.php
resources/views/users/edit.blade.php
resources/views/users/form.blade.php
resources/views/users/index.blade.php
resources/views/users/profile.blade.php
resources/views/vendor/notifications/email.blade.php
routes/web.php
storage/framework/cache/.gitignore
tests/Auth/AuthTest.php
tests/Auth/LdapTest.php
tests/Auth/SocialAuthTest.php
tests/Auth/UserInviteTest.php [new file with mode: 0644]
tests/BrowserKitTest.php
tests/CommandsTest.php
tests/Entity/BookShelfTest.php
tests/Entity/CommentSettingTest.php
tests/Entity/EntityTest.php
tests/Entity/ExportTest.php
tests/Entity/MarkdownTest.php
tests/Entity/PageContentTest.php
tests/Entity/PageDraftTest.php
tests/Entity/PageRevisionTest.php
tests/Entity/PageTemplateTest.php [new file with mode: 0644]
tests/Entity/SortTest.php
tests/Entity/TagTest.php
tests/LanguageTest.php
tests/Permissions/RestrictionsTest.php
tests/Permissions/RolesTest.php
tests/SharedTestHelpers.php
tests/TestEmailTest.php [new file with mode: 0644]
tests/ThemeTest.php [new file with mode: 0644]
tests/Unit/ConfigTest.php
tests/Unit/PageRepoTest.php [deleted file]
tests/Unit/UrlTest.php
tests/Uploads/AttachmentTest.php
tests/Uploads/ImageTest.php
tests/UserProfileTest.php
version
webpack.config.js

index 829a7509b2a3fa31229017180f776e2347679adb..c4c3f0b85ff1192839395d1cc7edb3b965802b8f 100644 (file)
@@ -89,7 +89,7 @@ REDIS_SERVERS=127.0.0.1:6379:0
 # Queue driver to use
 # Queue not really currently used but may be configurable in the future.
 # Would advise not to change this for now.
-QUEUE_DRIVER=sync
+QUEUE_CONNECTION=sync
 
 # Storage system to use
 # Can be 'local', 'local_secure' or 's3'
diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml
new file mode 100644 (file)
index 0000000..922aa50
--- /dev/null
@@ -0,0 +1,26 @@
+name: phpunit
+
+on: [push, pull_request]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        php: [7.2, 7.3]
+    steps:
+    - uses: actions/checkout@v1
+    - 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 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 & Test
+      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: phpunit
+      run: php${{ matrix.php }} ./vendor/bin/phpunit
index 1b53cbe7ac52416e90f3844194aab4a262c48d20..e5579e4a62f21ca24e6f804fdee275b9655519b6 100644 (file)
@@ -21,4 +21,5 @@ nbproject
 .buildpath
 .project
 .settings/
-webpack-stats.json
\ No newline at end of file
+webpack-stats.json
+.phpunit.result.cache
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644 (file)
index 29727f4..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-dist: trusty
-sudo: false
-language: php
-php:
-  - 7.0.20
-  - 7.1.9
-
-cache:
-  directories:
-    - $HOME/.composer/cache
-
-before_script:
-  - mysql -u root -e 'create database `bookstack-test`;'
-  - mysql -u root -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED BY 'bookstack-test';"
-  - mysql -u root -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
-  - mysql -u root -e "FLUSH PRIVILEGES;"
-  - phpenv config-rm xdebug.ini
-  - composer install --prefer-dist --no-interaction
-  - php artisan clear-compiled -n
-  - php artisan optimize -n
-  - php artisan migrate --force -n --database=mysql_testing
-  - php artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing
-
-after_failure:
-  - cat storage/logs/laravel.log
-
-script:
-  - phpunit
index 1ae1811e11461dbd94674c791b0c75143596d8b2..05f0129ddff7c322f8f900a3f5bdf4e6dba65fda 100644 (file)
@@ -3,13 +3,18 @@
 namespace BookStack\Actions;
 
 use BookStack\Auth\User;
+use BookStack\Entities\Entity;
 use BookStack\Model;
 
 /**
- * @property string  key
- * @property \User   user
- * @property \Entity entity
- * @property string  extra
+ * @property string $key
+ * @property User $user
+ * @property Entity $entity
+ * @property string $extra
+ * @property string $entity_type
+ * @property int $entity_id
+ * @property int $user_id
+ * @property int $book_id
  */
 class Activity extends Model
 {
index f4f82a6f4dfbbac6b63c2fb42a15fad0631f77e9..f56f1ca57e03c304c4f4fdcd7201293afe45f8ba 100644 (file)
@@ -1,8 +1,8 @@
 <?php namespace BookStack\Actions;
 
 use BookStack\Auth\Permissions\PermissionService;
+use BookStack\Entities\Book;
 use BookStack\Entities\Entity;
-use Session;
 
 class ActivityService
 {
@@ -12,7 +12,7 @@ class ActivityService
 
     /**
      * ActivityService constructor.
-     * @param \BookStack\Actions\Activity $activity
+     * @param Activity $activity
      * @param PermissionService $permissionService
      */
     public function __construct(Activity $activity, PermissionService $permissionService)
@@ -24,52 +24,57 @@ class ActivityService
 
     /**
      * Add activity data to database.
-     * @param Entity $entity
-     * @param        $activityKey
+     * @param \BookStack\Entities\Entity $entity
+     * @param string $activityKey
      * @param int $bookId
-     * @param bool $extra
      */
-    public function add(Entity $entity, $activityKey, $bookId = 0, $extra = false)
+    public function add(Entity $entity, string $activityKey, int $bookId = null)
     {
-        $activity = $this->activity->newInstance();
-        $activity->user_id = $this->user->id;
-        $activity->book_id = $bookId;
-        $activity->key = strtolower($activityKey);
-        if ($extra !== false) {
-            $activity->extra = $extra;
-        }
+        $activity = $this->newActivityForUser($activityKey, $bookId);
         $entity->activity()->save($activity);
         $this->setNotification($activityKey);
     }
 
     /**
-     * Adds a activity history with a message & without binding to a entity.
-     * @param            $activityKey
+     * Adds a activity history with a message, without binding to a entity.
+     * @param string $activityKey
+     * @param string $message
      * @param int $bookId
-     * @param bool|false $extra
      */
-    public function addMessage($activityKey, $bookId = 0, $extra = false)
+    public function addMessage(string $activityKey, string $message, int $bookId = null)
     {
-        $this->activity->user_id = $this->user->id;
-        $this->activity->book_id = $bookId;
-        $this->activity->key = strtolower($activityKey);
-        if ($extra !== false) {
-            $this->activity->extra = $extra;
-        }
-        $this->activity->save();
+        $this->newActivityForUser($activityKey, $bookId)->forceFill([
+            'extra' => $message
+        ])->save();
+
         $this->setNotification($activityKey);
     }
 
+    /**
+     * Get a new activity instance for the current user.
+     * @param string $key
+     * @param int|null $bookId
+     * @return Activity
+     */
+    protected function newActivityForUser(string $key, int $bookId = null)
+    {
+        return $this->activity->newInstance()->forceFill([
+            'key' => strtolower($key),
+            'user_id' => $this->user->id,
+            'book_id' => $bookId ?? 0,
+        ]);
+    }
 
     /**
      * Removes the entity attachment from each of its activities
      * and instead uses the 'extra' field with the entities name.
      * Used when an entity is deleted.
-     * @param Entity $entity
+     * @param \BookStack\Entities\Entity $entity
      * @return mixed
      */
     public function removeEntity(Entity $entity)
     {
+        // TODO - Rewrite to db query.
         $activities = $entity->activity;
         foreach ($activities as $activity) {
             $activity->extra = $entity->name;
@@ -90,7 +95,11 @@ class ActivityService
     {
         $activityList = $this->permissionService
             ->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type')
-            ->orderBy('created_at', 'desc')->with('user', 'entity')->skip($count * $page)->take($count)->get();
+            ->orderBy('created_at', 'desc')
+            ->with('user', 'entity')
+            ->skip($count * $page)
+            ->take($count)
+            ->get();
 
         return $this->filterSimilar($activityList);
     }
@@ -98,7 +107,7 @@ class ActivityService
     /**
      * Gets the latest activity for an entity, Filtering out similar
      * items to prevent a message activity list.
-     * @param Entity $entity
+     * @param \BookStack\Entities\Entity $entity
      * @param int $count
      * @param int $page
      * @return array
@@ -171,7 +180,7 @@ class ActivityService
         $notificationTextKey = 'activities.' . $activityKey . '_notification';
         if (trans()->has($notificationTextKey)) {
             $message = trans($notificationTextKey);
-            Session::flash('success', $message);
+            session()->flash('success', $message);
         }
     }
 }
index 532f31c423ca06ae026bb931f03deb84e16bfc87..324bfaa4ef9fb14c722148529c4bb895692a12e8 100644 (file)
@@ -1,8 +1,10 @@
 <?php namespace BookStack\Actions;
 
 use BookStack\Auth\Permissions\PermissionService;
+use BookStack\Entities\Book;
 use BookStack\Entities\Entity;
 use BookStack\Entities\EntityProvider;
+use DB;
 use Illuminate\Support\Collection;
 
 class ViewService
@@ -13,8 +15,8 @@ class ViewService
 
     /**
      * ViewService constructor.
-     * @param \BookStack\Actions\View $view
-     * @param \BookStack\Auth\Permissions\PermissionService $permissionService
+     * @param View $view
+     * @param PermissionService $permissionService
      * @param EntityProvider $entityProvider
      */
     public function __construct(View $view, PermissionService $permissionService, EntityProvider $entityProvider)
@@ -26,7 +28,7 @@ class ViewService
 
     /**
      * Add a view to the given entity.
-     * @param Entity $entity
+     * @param \BookStack\Entities\Entity $entity
      * @return int
      */
     public function add(Entity $entity)
@@ -43,7 +45,7 @@ class ViewService
         }
 
         // Otherwise create new view count
-        $entity->views()->save($this->view->create([
+        $entity->views()->save($this->view->newInstance([
             'user_id' => $user->id,
             'views' => 1
         ]));
@@ -59,12 +61,12 @@ class ViewService
      * @param string $action - used for permission checking
      * @return Collection
      */
-    public function getPopular(int $count = 10, int $page = 0, $filterModels = null, string $action = 'view')
+    public function getPopular(int $count = 10, int $page = 0, array $filterModels = null, string $action = 'view')
     {
         $skipCount = $count * $page;
         $query = $this->permissionService
             ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type', $action)
-            ->select('*', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count'))
+            ->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count'))
             ->groupBy('viewable_id', 'viewable_type')
             ->orderBy('view_count', 'desc');
 
index 8c56e9dac135e63179b945f0285ac7c8bc04c2e6..499fdeaa691ce8a0442b2494c9f46b09edf42f27 100644 (file)
@@ -13,7 +13,11 @@ class Application extends \Illuminate\Foundation\Application
      */
     public function configPath($path = '')
     {
-        return $this->basePath.DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.'Config'.($path ? DIRECTORY_SEPARATOR.$path : $path);
+        return $this->basePath
+            . DIRECTORY_SEPARATOR
+            . 'app'
+            . DIRECTORY_SEPARATOR
+            . 'Config'
+            . ($path ? DIRECTORY_SEPARATOR.$path : $path);
     }
-
-}
\ No newline at end of file
+}
index 4df014116c5c3d1c393e6e4594923c0d65a5d519..9aa3b9b98b56cf0d56453ae9e2de18c48c4d4524 100644 (file)
@@ -1,33 +1,18 @@
 <?php namespace BookStack\Auth\Access;
 
 use BookStack\Auth\User;
-use BookStack\Auth\UserRepo;
 use BookStack\Exceptions\ConfirmationEmailException;
-use BookStack\Exceptions\UserRegistrationException;
 use BookStack\Notifications\ConfirmEmail;
-use Carbon\Carbon;
-use Illuminate\Database\Connection as Database;
 
-class EmailConfirmationService
+class EmailConfirmationService extends UserTokenService
 {
-    protected $db;
-    protected $users;
-
-    /**
-     * EmailConfirmationService constructor.
-     * @param Database $db
-     * @param \BookStack\Auth\UserRepo $users
-     */
-    public function __construct(Database $db, UserRepo $users)
-    {
-        $this->db = $db;
-        $this->users = $users;
-    }
+    protected $tokenTable = 'email_confirmations';
+    protected $expiryTime = 24;
 
     /**
      * Create new confirmation for a user,
      * Also removes any existing old ones.
-     * @param \BookStack\Auth\User $user
+     * @param User $user
      * @throws ConfirmationEmailException
      */
     public function sendConfirmation(User $user)
@@ -36,76 +21,19 @@ class EmailConfirmationService
             throw new ConfirmationEmailException(trans('errors.email_already_confirmed'), '/login');
         }
 
-        $this->deleteConfirmationsByUser($user);
-        $token = $this->createEmailConfirmation($user);
+        $this->deleteByUser($user);
+        $token = $this->createTokenForUser($user);
 
         $user->notify(new ConfirmEmail($token));
     }
 
     /**
-     * Creates a new email confirmation in the database and returns the token.
-     * @param User $user
-     * @return string
-     */
-    public function createEmailConfirmation(User $user)
-    {
-        $token = $this->getToken();
-        $this->db->table('email_confirmations')->insert([
-            'user_id' => $user->id,
-            'token' => $token,
-            'created_at' => Carbon::now(),
-            'updated_at' => Carbon::now()
-        ]);
-        return $token;
-    }
-
-    /**
-     * Gets an email confirmation by looking up the token,
-     * Ensures the token has not expired.
-     * @param string $token
-     * @return array|null|\stdClass
-     * @throws UserRegistrationException
-     */
-    public function getEmailConfirmationFromToken($token)
-    {
-        $emailConfirmation = $this->db->table('email_confirmations')->where('token', '=', $token)->first();
-
-        // If not found show error
-        if ($emailConfirmation === null) {
-            throw new UserRegistrationException(trans('errors.email_confirmation_invalid'), '/register');
-        }
-
-        // If more than a day old
-        if (Carbon::now()->subDay()->gt(new Carbon($emailConfirmation->created_at))) {
-            $user = $this->users->getById($emailConfirmation->user_id);
-            $this->sendConfirmation($user);
-            throw new UserRegistrationException(trans('errors.email_confirmation_expired'), '/register/confirm');
-        }
-
-        $emailConfirmation->user = $this->users->getById($emailConfirmation->user_id);
-        return $emailConfirmation;
-    }
-
-    /**
-     * Delete all email confirmations that belong to a user.
-     * @param \BookStack\Auth\User $user
-     * @return mixed
+     * Check if confirmation is required in this instance.
+     * @return bool
      */
-    public function deleteConfirmationsByUser(User $user)
+    public function confirmationRequired() : bool
     {
-        return $this->db->table('email_confirmations')->where('user_id', '=', $user->id)->delete();
-    }
-
-    /**
-     * Creates a unique token within the email confirmation database.
-     * @return string
-     */
-    protected function getToken()
-    {
-        $token = str_random(24);
-        while ($this->db->table('email_confirmations')->where('token', '=', $token)->exists()) {
-            $token = str_random(25);
-        }
-        return $token;
+        return setting('registration-confirmation')
+            || setting('registration-restrict');
     }
 }
index 0d46b9f882d5ebe6afe1d08fb0d7b4714c9eeb74..9c8d1a81f43ebb8f30d66807a14349cab2882a39 100644 (file)
@@ -5,6 +5,7 @@ use BookStack\Auth\UserRepo;
 use BookStack\Exceptions\SocialDriverNotConfigured;
 use BookStack\Exceptions\SocialSignInAccountNotUsed;
 use BookStack\Exceptions\UserRegistrationException;
+use Illuminate\Support\Str;
 use Laravel\Socialite\Contracts\Factory as Socialite;
 use Laravel\Socialite\Contracts\User as SocialUser;
 
@@ -104,6 +105,7 @@ class SocialAuthService
         $socialAccount = $this->socialAccount->where('driver_id', '=', $socialId)->first();
         $isLoggedIn = auth()->check();
         $currentUser = user();
+        $titleCaseDriver = Str::title($socialDriver);
 
         // When a user is not logged in and a matching SocialAccount exists,
         // Simply log the user into the application.
@@ -117,26 +119,26 @@ class SocialAuthService
         if ($isLoggedIn && $socialAccount === null) {
             $this->fillSocialAccount($socialDriver, $socialUser);
             $currentUser->socialAccounts()->save($this->socialAccount);
-            session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => title_case($socialDriver)]));
+            session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => $titleCaseDriver]));
             return redirect($currentUser->getEditUrl());
         }
 
         // When a user is logged in and the social account exists and is already linked to the current user.
         if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id === $currentUser->id) {
-            session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => title_case($socialDriver)]));
+            session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => $titleCaseDriver]));
             return redirect($currentUser->getEditUrl());
         }
 
         // When a user is logged in, A social account exists but the users do not match.
         if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id != $currentUser->id) {
-            session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => title_case($socialDriver)]));
+            session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => $titleCaseDriver]));
             return redirect($currentUser->getEditUrl());
         }
 
         // Otherwise let the user know this social account is not used by anyone.
-        $message = trans('errors.social_account_not_used', ['socialAccount' => title_case($socialDriver)]);
+        $message = trans('errors.social_account_not_used', ['socialAccount' => $titleCaseDriver]);
         if (setting('registration-enabled')) {
-            $message .= trans('errors.social_account_register_instructions', ['socialAccount' => title_case($socialDriver)]);
+            $message .= trans('errors.social_account_register_instructions', ['socialAccount' => $titleCaseDriver]);
         }
         
         throw new SocialSignInAccountNotUsed($message, '/login');
@@ -157,7 +159,7 @@ class SocialAuthService
             abort(404, trans('errors.social_driver_not_found'));
         }
         if (!$this->checkDriverConfigured($driver)) {
-            throw new SocialDriverNotConfigured(trans('errors.social_driver_not_configured', ['socialAccount' => title_case($socialDriver)]));
+            throw new SocialDriverNotConfigured(trans('errors.social_driver_not_configured', ['socialAccount' => Str::title($socialDriver)]));
         }
 
         return $driver;
@@ -244,7 +246,7 @@ class SocialAuthService
     public function detachSocialAccount($socialDriver)
     {
         user()->socialAccounts()->where('driver', '=', $socialDriver)->delete();
-        session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => title_case($socialDriver)]));
+        session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => Str::title($socialDriver)]));
         return redirect(user()->getEditUrl());
     }
 
diff --git a/app/Auth/Access/UserInviteService.php b/app/Auth/Access/UserInviteService.php
new file mode 100644 (file)
index 0000000..20519fc
--- /dev/null
@@ -0,0 +1,22 @@
+<?php namespace BookStack\Auth\Access;
+
+use BookStack\Auth\User;
+use BookStack\Notifications\UserInvite;
+
+class UserInviteService extends UserTokenService
+{
+    protected $tokenTable = 'user_invites';
+    protected $expiryTime = 336; // Two weeks
+
+    /**
+     * Send an invitation to a user to sign into BookStack
+     * Removes existing invitation tokens.
+     * @param User $user
+     */
+    public function sendInvitation(User $user)
+    {
+        $this->deleteByUser($user);
+        $token = $this->createTokenForUser($user);
+        $user->notify(new UserInvite($token));
+    }
+}
diff --git a/app/Auth/Access/UserTokenService.php b/app/Auth/Access/UserTokenService.php
new file mode 100644 (file)
index 0000000..a1defbf
--- /dev/null
@@ -0,0 +1,134 @@
+<?php namespace BookStack\Auth\Access;
+
+use BookStack\Auth\User;
+use BookStack\Exceptions\UserTokenExpiredException;
+use BookStack\Exceptions\UserTokenNotFoundException;
+use Carbon\Carbon;
+use Illuminate\Database\Connection as Database;
+use Illuminate\Support\Str;
+use stdClass;
+
+class UserTokenService
+{
+
+    /**
+     * Name of table where user tokens are stored.
+     * @var string
+     */
+    protected $tokenTable = 'user_tokens';
+
+    /**
+     * Token expiry time in hours.
+     * @var int
+     */
+    protected $expiryTime = 24;
+
+    protected $db;
+
+    /**
+     * UserTokenService constructor.
+     * @param Database $db
+     */
+    public function __construct(Database $db)
+    {
+        $this->db = $db;
+    }
+
+    /**
+     * Delete all email confirmations that belong to a user.
+     * @param User $user
+     * @return mixed
+     */
+    public function deleteByUser(User $user)
+    {
+        return $this->db->table($this->tokenTable)
+            ->where('user_id', '=', $user->id)
+            ->delete();
+    }
+
+    /**
+     * Get the user id from a token, while check the token exists and has not expired.
+     * @param string $token
+     * @return int
+     * @throws UserTokenNotFoundException
+     * @throws UserTokenExpiredException
+     */
+    public function checkTokenAndGetUserId(string $token) : int
+    {
+        $entry = $this->getEntryByToken($token);
+
+        if (is_null($entry)) {
+            throw new UserTokenNotFoundException('Token "' . $token . '" not found');
+        }
+
+        if ($this->entryExpired($entry)) {
+            throw new UserTokenExpiredException("Token of id {$entry->id} has expired.", $entry->user_id);
+        }
+
+        return $entry->user_id;
+    }
+
+    /**
+     * Creates a unique token within the email confirmation database.
+     * @return string
+     */
+    protected function generateToken() : string
+    {
+        $token = Str::random(24);
+        while ($this->tokenExists($token)) {
+            $token = Str::random(25);
+        }
+        return $token;
+    }
+
+    /**
+     * Generate and store a token for the given user.
+     * @param User $user
+     * @return string
+     */
+    protected function createTokenForUser(User $user) : string
+    {
+        $token = $this->generateToken();
+        $this->db->table($this->tokenTable)->insert([
+            'user_id' => $user->id,
+            'token' => $token,
+            'created_at' => Carbon::now(),
+            'updated_at' => Carbon::now()
+        ]);
+        return $token;
+    }
+
+    /**
+     * Check if the given token exists.
+     * @param string $token
+     * @return bool
+     */
+    protected function tokenExists(string $token) : bool
+    {
+        return $this->db->table($this->tokenTable)
+            ->where('token', '=', $token)->exists();
+    }
+
+    /**
+     * Get a token entry for the given token.
+     * @param string $token
+     * @return object|null
+     */
+    protected function getEntryByToken(string $token)
+    {
+        return $this->db->table($this->tokenTable)
+            ->where('token', '=', $token)
+            ->first();
+    }
+
+    /**
+     * Check if the given token entry has expired.
+     * @param stdClass $tokenEntry
+     * @return bool
+     */
+    protected function entryExpired(stdClass $tokenEntry) : bool
+    {
+        return Carbon::now()->subHours($this->expiryTime)
+            ->gt(new Carbon($tokenEntry->created_at));
+    }
+}
index a5ab4ea9a8e51109c7225bc82ccca1799470205d..97cc1ca241e84209f235136550378f3cb8f43f81 100644 (file)
@@ -215,7 +215,6 @@ class PermissionService
      * @param Collection $books
      * @param array $roles
      * @param bool $deleteOld
-     * @throws \Throwable
      */
     protected function buildJointPermissionsForBooks($books, $roles, $deleteOld = false)
     {
@@ -634,42 +633,40 @@ class PermissionService
     }
 
     /**
-     * Get the children of a book in an efficient single query, Filtered by the permission system.
-     * @param integer $book_id
-     * @param bool $filterDrafts
-     * @param bool $fetchPageContent
-     * @return QueryBuilder
+     * Limited the given entity query so that the query will only
+     * return items that the user has permission for the given ability.
      */
-    public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false)
+    public function restrictEntityQuery(Builder $query, string $ability = 'view'): Builder
     {
-        $entities = $this->entityProvider;
-        $pageSelect = $this->db->table('pages')->selectRaw($entities->page->entityRawQuery($fetchPageContent))
-            ->where('book_id', '=', $book_id)->where(function ($query) use ($filterDrafts) {
-                $query->where('draft', '=', 0);
-                if (!$filterDrafts) {
-                    $query->orWhere(function ($query) {
-                        $query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id);
+        $this->clean();
+        return $query->where(function (Builder $parentQuery) use ($ability) {
+            $parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) use ($ability) {
+                $permissionQuery->whereIn('role_id', $this->getRoles())
+                    ->where('action', '=', $ability)
+                    ->where(function (Builder $query) {
+                        $query->where('has_permission', '=', true)
+                            ->orWhere(function (Builder $query) {
+                                $query->where('has_permission_own', '=', true)
+                                    ->where('created_by', '=', $this->currentUser()->id);
+                            });
                     });
-                }
-            });
-        $chapterSelect = $this->db->table('chapters')->selectRaw($entities->chapter->entityRawQuery())->where('book_id', '=', $book_id);
-        $query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U"))
-            ->mergeBindings($pageSelect)->mergeBindings($chapterSelect);
-
-        // Add joint permission filter
-        $whereQuery = $this->db->table('joint_permissions as jp')->selectRaw('COUNT(*)')
-            ->whereRaw('jp.entity_id=U.id')->whereRaw('jp.entity_type=U.entity_type')
-            ->where('jp.action', '=', 'view')->whereIn('jp.role_id', $this->getRoles())
-            ->where(function ($query) {
-                $query->where('jp.has_permission', '=', 1)->orWhere(function ($query) {
-                    $query->where('jp.has_permission_own', '=', 1)->where('jp.created_by', '=', $this->currentUser()->id);
-                });
             });
-        $query->whereRaw("({$whereQuery->toSql()}) > 0")->mergeBindings($whereQuery);
+        });
+    }
 
-        $query->orderBy('draft', 'desc')->orderBy('priority', 'asc');
-        $this->clean();
-        return  $query;
+    /**
+     * Extend the given page query to ensure draft items are not visible
+     * unless created by the given user.
+     */
+    public function enforceDraftVisiblityOnQuery(Builder $query): Builder
+    {
+        return $query->where(function (Builder $query) {
+            $query->where('draft', '=', false)
+                ->orWhere(function (Builder $query) {
+                    $query->where('draft', '=', true)
+                        ->where('created_by', '=', $this->currentUser()->id);
+                });
+        });
     }
 
     /**
@@ -684,12 +681,11 @@ class PermissionService
         if (strtolower($entityType) === 'page') {
             // Prevent drafts being visible to others.
             $query = $query->where(function ($query) {
-                $query->where('draft', '=', false);
-                if ($this->currentUser()) {
-                    $query->orWhere(function ($query) {
-                        $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser()->id);
+                $query->where('draft', '=', false)
+                    ->orWhere(function ($query) {
+                        $query->where('draft', '=', true)
+                            ->where('created_by', '=', $this->currentUser()->id);
                     });
-                }
             });
         }
 
index 18d5089bec2f89dcaea0d9258a448d9463041b1b..56ef193015f7103a98bb440ec60498e5bf765c25 100644 (file)
@@ -3,6 +3,7 @@
 use BookStack\Auth\Permissions;
 use BookStack\Auth\Role;
 use BookStack\Exceptions\PermissionsException;
+use Illuminate\Support\Str;
 
 class PermissionsRepo
 {
@@ -66,7 +67,7 @@ class PermissionsRepo
         $role->name = str_replace(' ', '-', strtolower($roleData['display_name']));
         // Prevent duplicate names
         while ($this->role->where('name', '=', $role->name)->count() > 0) {
-            $role->name .= strtolower(str_random(2));
+            $role->name .= strtolower(Str::random(2));
         }
         $role->save();
 
@@ -136,7 +137,7 @@ class PermissionsRepo
         // Prevent deleting admin role or default registration role.
         if ($role->system_name && in_array($role->system_name, $this->systemRoles)) {
             throw new PermissionsException(trans('errors.role_system_cannot_be_deleted'));
-        } else if ($role->id == setting('registration-role')) {
+        } else if ($role->id === intval(setting('registration-role'))) {
             throw new PermissionsException(trans('errors.role_registration_default_cannot_delete'));
         }
 
index 917d8aa26192c0388fac58fa5970267f3e25969c..712f5299b7b1d2ff5485179621aeddf9a1c5efbd 100644 (file)
@@ -75,7 +75,7 @@ class Role extends Model
      */
     public static function getRole($roleName)
     {
-        return static::where('name', '=', $roleName)->first();
+        return static::query()->where('name', '=', $roleName)->first();
     }
 
     /**
@@ -85,7 +85,7 @@ class Role extends Model
      */
     public static function getSystemRole($roleName)
     {
-        return static::where('system_name', '=', $roleName)->first();
+        return static::query()->where('system_name', '=', $roleName)->first();
     }
 
     /**
@@ -94,6 +94,15 @@ class Role extends Model
      */
     public static function visible()
     {
-        return static::where('hidden', '=', false)->orderBy('name')->get();
+        return static::query()->where('hidden', '=', false)->orderBy('name')->get();
+    }
+
+    /**
+     * Get the roles that can be restricted.
+     * @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection
+     */
+    public static function restrictable()
+    {
+        return static::query()->where('system_name', '!=', 'admin')->get();
     }
 }
index e5a8a393147969c42d0b079a3ce036359da61c5a..bce418a7421fb40ddeb34b0e0dacd192d7048e2a 100644 (file)
@@ -3,6 +3,7 @@
 use BookStack\Model;
 use BookStack\Notifications\ResetPassword;
 use BookStack\Uploads\Image;
+use Carbon\Carbon;
 use Illuminate\Auth\Authenticatable;
 use Illuminate\Auth\Passwords\CanResetPassword;
 use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
@@ -10,6 +11,20 @@ use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
 use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 use Illuminate\Notifications\Notifiable;
 
+/**
+ * Class User
+ * @package BookStack\Auth
+ * @property string $id
+ * @property string $name
+ * @property string $email
+ * @property string $password
+ * @property Carbon $created_at
+ * @property Carbon $updated_at
+ * @property bool $email_confirmed
+ * @property int $image_id
+ * @property string $external_auth_id
+ * @property string $system_name
+ */
 class User extends Model implements AuthenticatableContract, CanResetPasswordContract
 {
     use Authenticatable, CanResetPassword, Notifiable;
@@ -38,13 +53,24 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
      */
     protected $permissions;
 
+    /**
+     * This holds the default user when loaded.
+     * @var null|User
+     */
+    protected static $defaultUser = null;
+
     /**
      * Returns the default public user.
      * @return User
      */
     public static function getDefault()
     {
-        return static::where('system_name', '=', 'public')->first();
+        if (!is_null(static::$defaultUser)) {
+            return static::$defaultUser;
+        }
+        
+        static::$defaultUser = static::where('system_name', '=', 'public')->first();
+        return static::$defaultUser;
     }
 
     /**
index dec973f6c7acf67721e4dd31b20516c798714f48..a903e2c38324e27c1d646655d9758d8e1febb471 100644 (file)
@@ -1,32 +1,31 @@
 <?php namespace BookStack\Auth;
 
 use Activity;
-use BookStack\Entities\Repos\EntityRepo;
+use BookStack\Entities\Book;
+use BookStack\Entities\Bookshelf;
+use BookStack\Entities\Chapter;
+use BookStack\Entities\Page;
 use BookStack\Exceptions\NotFoundException;
 use BookStack\Exceptions\UserUpdateException;
 use BookStack\Uploads\Image;
 use Exception;
 use Illuminate\Database\Eloquent\Builder;
 use Images;
+use Log;
 
 class UserRepo
 {
 
     protected $user;
     protected $role;
-    protected $entityRepo;
 
     /**
      * UserRepo constructor.
-     * @param User $user
-     * @param Role $role
-     * @param EntityRepo $entityRepo
      */
-    public function __construct(User $user, Role $role, EntityRepo $entityRepo)
+    public function __construct(User $user, Role $role)
     {
         $this->user = $user;
         $this->role = $role;
-        $this->entityRepo = $entityRepo;
     }
 
     /**
@@ -81,7 +80,7 @@ class UserRepo
      * Creates a new user and attaches a role to them.
      * @param array $data
      * @param boolean $verifyEmail
-     * @return \BookStack\Auth\User
+     * @return User
      */
     public function registerNew(array $data, $verifyEmail = false)
     {
@@ -121,7 +120,7 @@ class UserRepo
 
     /**
      * Checks if the give user is the only admin.
-     * @param \BookStack\Auth\User $user
+     * @param User $user
      * @return bool
      */
     public function isOnlyAdmin(User $user)
@@ -175,7 +174,7 @@ class UserRepo
      * Create a new basic instance of user.
      * @param array $data
      * @param boolean $verifyEmail
-     * @return \BookStack\Auth\User
+     * @return User
      */
     public function create(array $data, $verifyEmail = false)
     {
@@ -189,7 +188,7 @@ class UserRepo
 
     /**
      * Remove the given user from storage, Delete all related content.
-     * @param \BookStack\Auth\User $user
+     * @param User $user
      * @throws Exception
      */
     public function destroy(User $user)
@@ -206,7 +205,7 @@ class UserRepo
 
     /**
      * Get the latest activity for a user.
-     * @param \BookStack\Auth\User $user
+     * @param User $user
      * @param int $count
      * @param int $page
      * @return array
@@ -218,36 +217,35 @@ class UserRepo
 
     /**
      * Get the recently created content for this given user.
-     * @param \BookStack\Auth\User $user
-     * @param int $count
-     * @return mixed
      */
-    public function getRecentlyCreated(User $user, $count = 20)
+    public function getRecentlyCreated(User $user, int $count = 20): array
     {
-        $createdByUserQuery = function (Builder $query) use ($user) {
-            $query->where('created_by', '=', $user->id);
+        $query = function (Builder $query) use ($user, $count) {
+            return $query->orderBy('created_at', 'desc')
+                ->where('created_by', '=', $user->id)
+                ->take($count)
+                ->get();
         };
 
         return [
-            'pages'    => $this->entityRepo->getRecentlyCreated('page', $count, 0, $createdByUserQuery),
-            'chapters' => $this->entityRepo->getRecentlyCreated('chapter', $count, 0, $createdByUserQuery),
-            'books'    => $this->entityRepo->getRecentlyCreated('book', $count, 0, $createdByUserQuery),
-            'shelves'  => $this->entityRepo->getRecentlyCreated('bookshelf', $count, 0, $createdByUserQuery)
+            'pages'    => $query(Page::visible()->where('draft', '=', false)),
+            'chapters' => $query(Chapter::visible()),
+            'books'    => $query(Book::visible()),
+            'shelves'  => $query(Bookshelf::visible()),
         ];
     }
 
     /**
      * Get asset created counts for the give user.
-     * @param \BookStack\Auth\User $user
-     * @return array
      */
-    public function getAssetCounts(User $user)
+    public function getAssetCounts(User $user): array
     {
+        $createdBy = ['created_by' => $user->id];
         return [
-            'pages'    => $this->entityRepo->getUserTotalCreated('page', $user),
-            'chapters' => $this->entityRepo->getUserTotalCreated('chapter', $user),
-            'books'    => $this->entityRepo->getUserTotalCreated('book', $user),
-            'shelves'    => $this->entityRepo->getUserTotalCreated('bookshelf', $user),
+            'pages'    =>  Page::visible()->where($createdBy)->count(),
+            'chapters'    =>  Chapter::visible()->where($createdBy)->count(),
+            'books'    =>  Book::visible()->where($createdBy)->count(),
+            'shelves'    =>  Bookshelf::visible()->where($createdBy)->count(),
         ];
     }
 
@@ -260,16 +258,6 @@ class UserRepo
         return $this->role->newQuery()->orderBy('name', 'asc')->get();
     }
 
-    /**
-     * Get all the roles which can be given restricted access to
-     * other entities in the system.
-     * @return mixed
-     */
-    public function getRestrictableRoles()
-    {
-        return $this->role->where('system_name', '!=', 'admin')->get();
-    }
-
     /**
      * Get an avatar image for a user and set it as their avatar.
      * Returns early if avatars disabled or not set in config.
@@ -288,7 +276,7 @@ class UserRepo
             $user->save();
             return true;
         } catch (Exception $e) {
-            \Log::error('Failed to save user avatar image');
+            Log::error('Failed to save user avatar image');
             return false;
         }
     }
index 23025a6c45e4c819fd5668c77f1c36546edbd7f8..9dae697da54f7fab813c1c75487b42261efc7a55 100755 (executable)
@@ -52,11 +52,14 @@ return [
     'locale' => env('APP_LANG', 'en'),
 
     // Locales available
-    'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'hu', 'nl', 'pt_BR', 'sk', 'cs', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'uk', 'zh_CN', 'zh_TW'],
+    'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'hu', 'nl', 'pt_BR', 'sk', 'cs', 'sv', 'ko', 'ja', 'pl', 'it', 'ru', 'uk', 'zh_CN', 'zh_TW', 'tr'],
 
     //  Application Fallback Locale
     'fallback_locale' => 'en',
 
+    // Faker Locale
+    'faker_locale' => 'en_GB',
+
     // Enable right-to-left text control.
     'rtl' => false,
 
@@ -72,10 +75,6 @@ return [
     // Encryption cipher
     'cipher' => 'AES-256-CBC',
 
-    // Logging configuration
-    // Options: single, daily, syslog, errorlog
-    'log' => env('APP_LOGGING', 'single'),
-
     // Application Services Provides
     'providers' => [
 
@@ -108,7 +107,6 @@ return [
         Barryvdh\Snappy\ServiceProvider::class,
         Aacotroneo\Saml2\Saml2ServiceProvider::class,
 
-
         // BookStack replacement service providers (Extends Laravel)
         BookStack\Providers\PaginationServiceProvider::class,
         BookStack\Providers\TranslationServiceProvider::class,
@@ -138,6 +136,7 @@ return [
 
         // Laravel
         'App'       => Illuminate\Support\Facades\App::class,
+        'Arr'       => Illuminate\Support\Arr::class,
         'Artisan'   => Illuminate\Support\Facades\Artisan::class,
         'Auth'      => Illuminate\Support\Facades\Auth::class,
         'Blade'     => Illuminate\Support\Facades\Blade::class,
@@ -167,6 +166,7 @@ return [
         'Schema'    => Illuminate\Support\Facades\Schema::class,
         'Session'   => Illuminate\Support\Facades\Session::class,
         'Storage'   => Illuminate\Support\Facades\Storage::class,
+        'Str'       => Illuminate\Support\Str::class,
         'URL'       => Illuminate\Support\Facades\URL::class,
         'Validator' => Illuminate\Support\Facades\Validator::class,
         'View'      => Illuminate\Support\Facades\View::class,
@@ -182,6 +182,7 @@ return [
         'Setting'  => BookStack\Facades\Setting::class,
         'Views'    => BookStack\Facades\Views::class,
         'Images'   => BookStack\Facades\Images::class,
+        'Permissions' => BookStack\Facades\Permissions::class,
 
     ],
 
index 7bf1ae7722c958cfd8940c4d30d43af78a76dbf6..5535a6f9ce88b1c5ea6fd972d212f00337dfdb24 100644 (file)
@@ -36,6 +36,7 @@ return [
         'api' => [
             'driver' => 'token',
             'provider' => 'users',
+            'hash' => false,
         ],
     ],
 
@@ -69,4 +70,4 @@ return [
         ],
     ],
 
-];
\ No newline at end of file
+];
index 3d9eb78f95abd18bdb79d785a693de3f174641a6..7aaaa5693fe1cf907e07da54880710f802c9694a 100644 (file)
@@ -24,9 +24,13 @@ return [
 
         'pusher' => [
             'driver' => 'pusher',
-            'key' => env('PUSHER_KEY'),
-            'secret' => env('PUSHER_SECRET'),
+            'key' => env('PUSHER_APP_KEY'),
+            'secret' => env('PUSHER_APP_SECRET'),
             'app_id' => env('PUSHER_APP_ID'),
+            'options' => [
+                'cluster' => env('PUSHER_APP_CLUSTER'),
+                'useTLS' => true,
+            ],
         ],
 
         'redis' => [
@@ -38,6 +42,11 @@ return [
             'driver' => 'log',
         ],
 
+        'null' => [
+            'driver' => 'null',
+        ],
+
+
     ],
 
 ];
index 43f420457eba43f7b757e48bf0960b6e5a55a2b8..33d3a1a0bb7b02e6471f1088492fa7b5078e0519 100644 (file)
@@ -14,8 +14,12 @@ if (env('CACHE_DRIVER') === 'memcached') {
     $memcachedServers = explode(',', trim(env('MEMCACHED_SERVERS', '127.0.0.1:11211:100'), ','));
     foreach ($memcachedServers as $index => $memcachedServer) {
         $memcachedServerDetails = explode(':', $memcachedServer);
-        if (count($memcachedServerDetails) < 2) $memcachedServerDetails[] = '11211';
-        if (count($memcachedServerDetails) < 3) $memcachedServerDetails[] = '100';
+        if (count($memcachedServerDetails) < 2) {
+            $memcachedServerDetails[] = '11211';
+        }
+        if (count($memcachedServerDetails) < 3) {
+            $memcachedServerDetails[] = '100';
+        }
         $memcachedServers[$index] = array_combine($memcachedServerKeys, $memcachedServerDetails);
     }
 }
@@ -62,6 +66,6 @@ return [
 
     // Cache key prefix
     // Used to prevent collisions in shared cache systems.
-    'prefix' => env('CACHE_PREFIX', 'bookstack'),
+    'prefix' => env('CACHE_PREFIX', 'bookstack_cache'),
 
 ];
index 93a44854f092a8d166b9ce994a41da9dacb67a99..ed654ffb9172b4789a62c922d971adb8f550976d 100644 (file)
 // REDIS
 // Split out configuration into an array
 if (env('REDIS_SERVERS', false)) {
-
     $redisDefaults = ['host' => '127.0.0.1', 'port' => '6379', 'database' => '0', 'password' => null];
     $redisServers = explode(',', trim(env('REDIS_SERVERS', '127.0.0.1:6379:0'), ','));
-    $redisConfig = [];
+    $redisConfig = ['client' => 'predis'];
     $cluster = count($redisServers) > 1;
 
     if ($cluster) {
@@ -59,14 +58,9 @@ return [
     // Many of those shown here are unsupported by BookStack.
     'connections' => [
 
-        'sqlite' => [
-            'driver'   => 'sqlite',
-            'database' => storage_path('database.sqlite'),
-            'prefix'   => '',
-        ],
-
         'mysql' => [
             'driver'    => 'mysql',
+            'url' => env('DATABASE_URL'),
             'host'      => $mysql_host,
             'database'  => env('DB_DATABASE', 'forge'),
             'username'  => env('DB_USERNAME', 'forge'),
@@ -76,43 +70,28 @@ return [
             'charset'   => 'utf8mb4',
             'collation' => 'utf8mb4_unicode_ci',
             'prefix'    => '',
+            'prefix_indexes' => true,
             'strict'    => false,
             'engine' => null,
+            'options' => extension_loaded('pdo_mysql') ? array_filter([
+                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
+            ]) : [],
         ],
 
         'mysql_testing' => [
             'driver'    => 'mysql',
+            'url' => env('TEST_DATABASE_URL'),
             'host'      => '127.0.0.1',
             'database'  => 'bookstack-test',
             'username'  => env('MYSQL_USER', 'bookstack-test'),
             'password'  => env('MYSQL_PASSWORD', 'bookstack-test'),
-            'charset'   => 'utf8',
-            'collation' => 'utf8_unicode_ci',
+            'charset'   => 'utf8mb4',
+            'collation' => 'utf8mb4_unicode_ci',
             'prefix'    => '',
+            'prefix_indexes' => true,
             'strict'    => false,
         ],
 
-        'pgsql' => [
-            'driver'   => 'pgsql',
-            'host'     => env('DB_HOST', 'localhost'),
-            'database' => env('DB_DATABASE', 'forge'),
-            'username' => env('DB_USERNAME', 'forge'),
-            'password' => env('DB_PASSWORD', ''),
-            'charset'  => 'utf8',
-            'prefix'   => '',
-            'schema'   => 'public',
-        ],
-
-        'sqlsrv' => [
-            'driver'   => 'sqlsrv',
-            'host'     => env('DB_HOST', 'localhost'),
-            'database' => env('DB_DATABASE', 'forge'),
-            'username' => env('DB_USERNAME', 'forge'),
-            'password' => env('DB_PASSWORD', ''),
-            'charset'  => 'utf8',
-            'prefix'   => '',
-        ],
-
     ],
 
     // Migration Repository Table
index ec942dcd3bbbdacbab3c98fd764a0fab4b6be325..fe624eb7d460c2fa185344f7dd86f3d1906e1bcb 100644 (file)
@@ -79,6 +79,7 @@ return [
         'files'           => false, // Show the included files
         'config'          => false, // Display config settings
         'cache'           => false, // Display cache events
+        'models'          => true, // Display models
     ],
 
      // Configure some DataCollectors
index 77f0cff9c73738cd40eb61c54fe56b3053a5c76a..87be53df52b373d7c701a590f3051c7066fff711 100644 (file)
@@ -69,7 +69,7 @@ return [
          * should be an absolute path.
          * This is only checked on command line call by dompdf.php, but not by
          * direct class use like:
-         * $dompdf = new DOMPDF();     $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output();
+         * $dompdf = new DOMPDF();  $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output();
          */
         "DOMPDF_CHROOT" => realpath(base_path()),
 
diff --git a/app/Config/hashing.php b/app/Config/hashing.php
new file mode 100644 (file)
index 0000000..756718c
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * Hashing configuration options.
+ *
+ * Changes to these config files are not supported by BookStack and may break upon updates.
+ * Configuration should be altered via the `.env` file or environment variables.
+ * Do not edit this file unless you're happy to maintain any changes yourself.
+ */
+
+return [
+
+    // Default Hash Driver
+    // This option controls the default hash driver that will be used to hash
+    // passwords for your application. By default, the bcrypt algorithm is used.
+    // Supported: "bcrypt", "argon", "argon2id"
+    'driver' => 'bcrypt',
+
+    // Bcrypt Options
+    // Here you may specify the configuration options that should be used when
+    // passwords are hashed using the Bcrypt algorithm. This will allow you
+    // to control the amount of time it takes to hash the given password.
+    'bcrypt' => [
+        'rounds' => env('BCRYPT_ROUNDS', 10),
+    ],
+
+    // Argon Options
+    // Here you may specify the configuration options that should be used when
+    // passwords are hashed using the Argon algorithm. These will allow you
+    // to control the amount of time it takes to hash the given password.
+    'argon' => [
+        'memory' => 1024,
+        'threads' => 2,
+        'time' => 2,
+    ],
+
+];
diff --git a/app/Config/logging.php b/app/Config/logging.php
new file mode 100644 (file)
index 0000000..0b55dc2
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+use Monolog\Handler\NullHandler;
+use Monolog\Handler\StreamHandler;
+
+/**
+ * Logging configuration options.
+ *
+ * Changes to these config files are not supported by BookStack and may break upon updates.
+ * Configuration should be altered via the `.env` file or environment variables.
+ * Do not edit this file unless you're happy to maintain any changes yourself.
+ */
+
+return [
+
+    // Default Log Channel
+    // This option defines the default log channel that gets used when writing
+    // messages to the logs. The name specified in this option should match
+    // one of the channels defined in the "channels" configuration array.
+    'default' => env('LOG_CHANNEL', 'single'),
+
+    // Log Channels
+    // Here you may configure the log channels for your application. Out of
+    // the box, Laravel uses the Monolog PHP logging library. This gives
+    // you a variety of powerful log handlers / formatters to utilize.
+    // Available Drivers: "single", "daily", "slack", "syslog",
+    //                    "errorlog", "monolog",
+    //                    "custom", "stack"
+    'channels' => [
+        'stack' => [
+            'driver' => 'stack',
+            'channels' => ['daily'],
+            'ignore_exceptions' => false,
+        ],
+
+        'single' => [
+            'driver' => 'single',
+            'path' => storage_path('logs/laravel.log'),
+            'level' => 'debug',
+            'days' => 14,
+        ],
+
+        'daily' => [
+            'driver' => 'daily',
+            'path' => storage_path('logs/laravel.log'),
+            'level' => 'debug',
+            'days' => 7,
+        ],
+
+        'slack' => [
+            'driver' => 'slack',
+            'url' => env('LOG_SLACK_WEBHOOK_URL'),
+            'username' => 'Laravel Log',
+            'emoji' => ':boom:',
+            'level' => 'critical',
+        ],
+
+        'stderr' => [
+            'driver' => 'monolog',
+            'handler' => StreamHandler::class,
+            'with' => [
+                'stream' => 'php://stderr',
+            ],
+        ],
+
+        'syslog' => [
+            'driver' => 'syslog',
+            'level' => 'debug',
+        ],
+
+        'errorlog' => [
+            'driver' => 'errorlog',
+            'level' => 'debug',
+        ],
+
+        'null' => [
+            'driver' => 'monolog',
+            'handler' => NullHandler::class,
+        ],
+    ],
+
+];
index 49407bd8eae1f34d08931795f95c4d893ee63a09..a91bdf23797ef182325da8f49d621d541f1bc8d2 100644 (file)
@@ -23,7 +23,7 @@ return [
     // Global "From" address & name
     'from' => [
         'address' => env('MAIL_FROM', '[email protected]'),
-        'name' => env('MAIL_FROM_NAME','BookStack')
+        'name' => env('MAIL_FROM_NAME', 'BookStack')
     ],
 
     // Email encryption protocol
@@ -46,4 +46,10 @@ return [
         ],
     ],
 
+    // Log Channel
+    // If you are using the "log" driver, you may specify the logging channel
+    // if you prefer to keep mail messages separate from other log entries
+    // for simpler reading. Otherwise, the default channel will be used.
+    'log_channel' => env('MAIL_LOG_CHANNEL'),
+
 ];
index 721eac13685cccaebc05b4e9ec417289017c4929..46f6962c5f327f4fd644f029d3159eece4f6701a 100644 (file)
@@ -12,11 +12,12 @@ return [
 
     // Default driver to use for the queue
     // Options: null, sync, redis
-    'default' => env('QUEUE_DRIVER', 'sync'),
+    'default' => env('QUEUE_CONNECTION', 'sync'),
 
     // Queue connection configuration
     'connections' => [
 
+
         'sync' => [
             'driver' => 'sync',
         ],
@@ -25,38 +26,15 @@ return [
             'driver' => 'database',
             'table' => 'jobs',
             'queue' => 'default',
-            'expire' => 60,
-        ],
-
-        'beanstalkd' => [
-            'driver' => 'beanstalkd',
-            'host'   => 'localhost',
-            'queue'  => 'default',
-            'ttr'    => 60,
-        ],
-
-        'sqs' => [
-            'driver' => 'sqs',
-            'key'    => 'your-public-key',
-            'secret' => 'your-secret-key',
-            'queue'  => 'your-queue-url',
-            'region' => 'us-east-1',
-        ],
-
-        'iron' => [
-            'driver'  => 'iron',
-            'host'    => 'mq-aws-us-east-1.iron.io',
-            'token'   => 'your-token',
-            'project' => 'your-project-id',
-            'queue'   => 'your-queue-name',
-            'encrypt' => true,
+            'retry_after' => 90,
         ],
 
         'redis' => [
             'driver' => 'redis',
             'connection' => 'default',
-            'queue'  => 'default',
-            'expire' => 60,
+            'queue' => env('REDIS_QUEUE', 'default'),
+            'retry_after' => 90,
+            'block_for' => null,
         ],
 
     ],
index b3dc9f08779ff2e6225bdb37e39d159e40d7246d..0f80a9fc15f4f66c57369d71a19096721b2df4f4 100644 (file)
@@ -22,23 +22,6 @@ return [
     // Callback URL for social authentication methods
     'callback_url' => env('APP_URL', false),
 
-    'mailgun'  => [
-        'domain' => '',
-        'secret' => '',
-    ],
-
-    'ses'      => [
-        'key'    => '',
-        'secret' => '',
-        'region' => 'us-east-1',
-    ],
-
-    'stripe'   => [
-        'model'  => \BookStack\Auth\User::class,
-        'key'    => '',
-        'secret' => '',
-    ],
-
     'github'   => [
         'client_id'     => env('GITHUB_APP_ID', false),
         'client_secret' => env('GITHUB_APP_SECRET', false),
@@ -143,9 +126,9 @@ return [
         'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
         'display_name_attribute' => env('LDAP_DISPLAY_NAME_ATTRIBUTE', 'cn'),
         'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
-        'user_to_groups' => env('LDAP_USER_TO_GROUPS',false),
+        'user_to_groups' => env('LDAP_USER_TO_GROUPS', false),
         'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'),
-        'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS',false),
+        'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS', false),
         'tls_insecure' => env('LDAP_TLS_INSECURE', false),
     ],
 
index bdb5e554b3238f274478e835f5dd867c4b737183..37f1627bb5f5c151ae01748cdc06b8f5c7e7eeb2 100644 (file)
@@ -35,13 +35,18 @@ return [
     // Session database table, if database driver is in use
     'table' => 'sessions',
 
+    // Session Cache Store
+    // When using the "apc" or "memcached" session drivers, you may specify a
+    // cache store that should be used for these sessions. This value must
+    // correspond with one of the application's configured cache stores.
+    'store' => null,
+
     // Session Sweeping Lottery
     // Some session drivers must manually sweep their storage location to get
     // rid of old sessions from storage. Here are the chances that it will
     // happen on a given request. By default, the odds are 2 out of 100.
     'lottery' => [2, 100],
 
-
     // Session Cookie Name
     // Here you may change the name of the cookie used to identify a session
     // instance by ID. The name specified here will get used every time a
index b48253eb190f1cc408c50fc944bc41b8a8d79f28..c6080df1db6df81b434a935dde272c0f038c5c0a 100644 (file)
@@ -14,9 +14,9 @@ return [
     'app-logo'             => '',
     'app-name-header'      => true,
     'app-editor'           => 'wysiwyg',
-    'app-color'            => '#0288D1',
-    'app-color-light'      => 'rgba(21, 101, 192, 0.15)',
+    'app-color'            => '#206ea7',
+    'app-color-light'      => 'rgba(32,110,167,0.15)',
     'app-custom-head'      => false,
     'registration-enabled' => false,
 
-];
\ No newline at end of file
+];
index 7d3d5e4ae98bf0e670c436dba1df93fb7c86d92b..4e54457b80391aa223442d92e065f376a12b86a9 100644 (file)
@@ -1,22 +1,25 @@
 <?php namespace BookStack\Entities;
 
 use BookStack\Uploads\Image;
+use Exception;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use Illuminate\Database\Eloquent\Relations\HasMany;
+use Illuminate\Support\Collection;
 
-class Book extends Entity
+/**
+ * Class Book
+ * @property string $description
+ * @property int $image_id
+ * @property Image|null $cover
+ * @package BookStack\Entities
+ */
+class Book extends Entity implements HasCoverImage
 {
     public $searchFactor = 2;
 
     protected $fillable = ['name', 'description', 'image_id'];
 
-    /**
-     * Get the morph class for this model.
-     * @return string
-     */
-    public function getMorphClass()
-    {
-        return 'BookStack\\Book';
-    }
-
     /**
      * Get the url for this book.
      * @param string|bool $path
@@ -45,7 +48,7 @@ class Book extends Entity
 
         try {
             $cover = $this->cover ? url($this->cover->getThumb($width, $height, false)) : $default;
-        } catch (\Exception $err) {
+        } catch (Exception $err) {
             $cover = $default;
         }
         return $cover;
@@ -53,16 +56,23 @@ class Book extends Entity
 
     /**
      * Get the cover image of the book
-     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
      */
-    public function cover()
+    public function cover(): BelongsTo
     {
         return $this->belongsTo(Image::class, 'image_id');
     }
 
+    /**
+     * Get the type of the image model that is used when storing a cover image.
+     */
+    public function coverImageTypeKey(): string
+    {
+        return 'cover_book';
+    }
+
     /**
      * Get all pages within this book.
-     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     * @return HasMany
      */
     public function pages()
     {
@@ -71,7 +81,7 @@ class Book extends Entity
 
     /**
      * Get the direct child pages of this book.
-     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     * @return HasMany
      */
     public function directPages()
     {
@@ -80,7 +90,7 @@ class Book extends Entity
 
     /**
      * Get all chapters within this book.
-     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     * @return HasMany
      */
     public function chapters()
     {
@@ -89,7 +99,7 @@ class Book extends Entity
 
     /**
      * Get the shelves this book is contained within.
-     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+     * @return BelongsToMany
      */
     public function shelves()
     {
@@ -97,22 +107,24 @@ class Book extends Entity
     }
 
     /**
-     * Get an excerpt of this book's description to the specified length or less.
-     * @param int $length
-     * @return string
+     * Get the direct child items within this book.
+     * @return Collection
      */
-    public function getExcerpt(int $length = 100)
+    public function getDirectChildren(): Collection
     {
-        $description = $this->description;
-        return mb_strlen($description) > $length ? mb_substr($description, 0, $length-3) . '...' : $description;
+        $pages = $this->directPages()->visible()->get();
+        $chapters = $this->chapters()->visible()->get();
+        return $pages->contact($chapters)->sortBy('priority')->sortByDesc('draft');
     }
 
     /**
-     * Return a generalised, common raw query that can be 'unioned' across entities.
+     * Get an excerpt of this book's description to the specified length or less.
+     * @param int $length
      * @return string
      */
-    public function entityRawQuery()
+    public function getExcerpt(int $length = 100)
     {
-        return "'BookStack\\\\Book' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text,'' as html, '0' as book_id, '0' as priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at";
+        $description = $this->description;
+        return mb_strlen($description) > $length ? mb_substr($description, 0, $length-3) . '...' : $description;
     }
 }
diff --git a/app/Entities/BookChild.php b/app/Entities/BookChild.php
new file mode 100644 (file)
index 0000000..6eac437
--- /dev/null
@@ -0,0 +1,60 @@
+<?php namespace BookStack\Entities;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+
+/**
+ * Class BookChild
+ * @property int $book_id
+ * @property int $priority
+ * @property Book $book
+ * @method Builder whereSlugs(string $bookSlug, string $childSlug)
+ */
+class BookChild extends Entity
+{
+
+    /**
+     * Scope a query to find items where the the child has the given childSlug
+     * where its parent has the bookSlug.
+     */
+    public function scopeWhereSlugs(Builder $query, string $bookSlug, string $childSlug)
+    {
+        return $query->with('book')
+            ->whereHas('book', function (Builder $query) use ($bookSlug) {
+                $query->where('slug', '=', $bookSlug);
+            })
+            ->where('slug', '=', $childSlug);
+    }
+
+    /**
+     * Get the book this page sits in.
+     * @return BelongsTo
+     */
+    public function book(): BelongsTo
+    {
+        return $this->belongsTo(Book::class);
+    }
+
+    /**
+     * Change the book that this entity belongs to.
+     */
+    public function changeBook(int $newBookId): Entity
+    {
+        $this->book_id = $newBookId;
+        $this->refreshSlug();
+        $this->save();
+        $this->refresh();
+
+        // Update related activity
+        $this->activity()->update(['book_id' => $newBookId]);
+
+        // Update all child pages if a chapter
+        if ($this instanceof Chapter) {
+            foreach ($this->pages as $page) {
+                $page->changeBook($newBookId);
+            }
+        }
+
+        return $this;
+    }
+}
index db6685688b3bcd2e6c449338f7ac9e83eef58803..62c7e2fe4f56c3e0a78f91105d6ba3c0da6dfad9 100644 (file)
@@ -1,8 +1,10 @@
 <?php namespace BookStack\Entities;
 
 use BookStack\Uploads\Image;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
-class Bookshelf extends Entity
+class Bookshelf extends Entity implements HasCoverImage
 {
     protected $table = 'bookshelves';
 
@@ -10,15 +12,6 @@ class Bookshelf extends Entity
 
     protected $fillable = ['name', 'description', 'image_id'];
 
-    /**
-     * Get the morph class for this model.
-     * @return string
-     */
-    public function getMorphClass()
-    {
-        return 'BookStack\\Bookshelf';
-    }
-
     /**
      * Get the books in this shelf.
      * Should not be used directly since does not take into account permissions.
@@ -31,6 +24,14 @@ class Bookshelf extends Entity
             ->orderBy('order', 'asc');
     }
 
+    /**
+     * Related books that are visible to the current user.
+     */
+    public function visibleBooks(): BelongsToMany
+    {
+        return $this->books()->visible();
+    }
+
     /**
      * Get the url for this bookshelf.
      * @param string|bool $path
@@ -68,13 +69,20 @@ class Bookshelf extends Entity
 
     /**
      * Get the cover image of the shelf
-     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
      */
-    public function cover()
+    public function cover(): BelongsTo
     {
         return $this->belongsTo(Image::class, 'image_id');
     }
 
+    /**
+     * Get the type of the image model that is used when storing a cover image.
+     */
+    public function coverImageTypeKey(): string
+    {
+        return 'cover_shelf';
+    }
+
     /**
      * Get an excerpt of this book's description to the specified length or less.
      * @param int $length
@@ -87,21 +95,26 @@ class Bookshelf extends Entity
     }
 
     /**
-     * Return a generalised, common raw query that can be 'unioned' across entities.
-     * @return string
+     * Check if this shelf contains the given book.
+     * @param Book $book
+     * @return bool
      */
-    public function entityRawQuery()
+    public function contains(Book $book): bool
     {
-        return "'BookStack\\\\BookShelf' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text,'' as html, '0' as book_id, '0' as priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at";
+        return $this->books()->where('id', '=', $book->id)->count() > 0;
     }
 
     /**
-     * Check if this shelf contains the given book.
+     * Add a book to the end of this shelf.
      * @param Book $book
-     * @return bool
      */
-    public function contains(Book $book)
+    public function appendBook(Book $book)
     {
-        return $this->books()->where('id', '=', $book->id)->count() > 0;
+        if ($this->contains($book)) {
+            return;
+        }
+
+        $maxOrder = $this->books()->max('order');
+        $this->books()->attach($book->id, ['order' => $maxOrder + 1]);
     }
 }
index 97ddbc2dc86764ed930c82d3c549f47b35c83fe1..43d63d026021dd07edcb1165841018ee213b13a3 100644 (file)
@@ -1,5 +1,6 @@
 <?php namespace BookStack\Entities;
 
+use BookStack\Entities\Managers\EntityContext;
 use Illuminate\View\View;
 
 class BreadcrumbsViewComposer
@@ -9,9 +10,9 @@ class BreadcrumbsViewComposer
 
     /**
      * BreadcrumbsViewComposer constructor.
-     * @param EntityContextManager $entityContextManager
+     * @param EntityContext $entityContextManager
      */
-    public function __construct(EntityContextManager $entityContextManager)
+    public function __construct(EntityContext $entityContextManager)
     {
         $this->entityContextManager = $entityContextManager;
     }
@@ -23,8 +24,9 @@ class BreadcrumbsViewComposer
     public function compose(View $view)
     {
         $crumbs = $view->getData()['crumbs'];
-        if (array_first($crumbs) instanceof Book) {
-            $shelf = $this->entityContextManager->getContextualShelfForBook(array_first($crumbs));
+        $firstCrumb = $crumbs[0] ?? null;
+        if ($firstCrumb instanceof Book) {
+            $shelf = $this->entityContextManager->getContextualShelfForBook($firstCrumb);
             if ($shelf) {
                 array_unshift($crumbs, $shelf);
                 $view->with('crumbs', $crumbs);
index b204f1903034663e755a6ca85bb38cf7f26683f3..848bc6448bd467b853ef25c8982c0aa8b5ad6e4c 100644 (file)
@@ -1,29 +1,18 @@
 <?php namespace BookStack\Entities;
 
-class Chapter extends Entity
+use Illuminate\Support\Collection;
+
+/**
+ * Class Chapter
+ * @property Collection<Page> $pages
+ * @package BookStack\Entities
+ */
+class Chapter extends BookChild
 {
     public $searchFactor = 1.3;
 
     protected $fillable = ['name', 'description', 'priority', 'book_id'];
 
-    /**
-     * Get the morph class for this model.
-     * @return string
-     */
-    public function getMorphClass()
-    {
-        return 'BookStack\\Chapter';
-    }
-
-    /**
-     * Get the book this chapter is within.
-     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
-     */
-    public function book()
-    {
-        return $this->belongsTo(Book::class);
-    }
-
     /**
      * Get the pages that this chapter contains.
      * @param string $dir
@@ -63,20 +52,22 @@ class Chapter extends Entity
     }
 
     /**
-     * Return a generalised, common raw query that can be 'unioned' across entities.
-     * @return string
+     * Check if this chapter has any child pages.
+     * @return bool
      */
-    public function entityRawQuery()
+    public function hasChildren()
     {
-        return "'BookStack\\\\Chapter' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text, '' as html, book_id, priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at";
+        return count($this->pages) > 0;
     }
 
     /**
-     * Check if this chapter has any child pages.
-     * @return bool
+     * Get the visible pages in this chapter.
      */
-    public function hasChildren()
+    public function getVisiblePages(): Collection
     {
-        return count($this->pages) > 0;
+        return $this->pages()->visible()
+        ->orderBy('draft', 'desc')
+        ->orderBy('priority', 'asc')
+        ->get();
     }
 }
index 482d217662ae20b54f62578d88367b8daacb1098..5013c39cfcf2bf309f1931594ebe702dd00dc44c 100644 (file)
@@ -6,8 +6,11 @@ use BookStack\Actions\Tag;
 use BookStack\Actions\View;
 use BookStack\Auth\Permissions\EntityPermission;
 use BookStack\Auth\Permissions\JointPermission;
+use BookStack\Facades\Permissions;
 use BookStack\Ownable;
 use Carbon\Carbon;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Collection;
 use Illuminate\Database\Eloquent\Relations\MorphMany;
 
 /**
@@ -15,7 +18,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
  * The base class for book-like items such as pages, chapters & books.
  * This is not a database model in itself but extended.
  *
- * @property integer $id
+ * @property int $id
  * @property string $name
  * @property string $slug
  * @property Carbon $created_at
@@ -23,6 +26,11 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
  * @property int $created_by
  * @property int $updated_by
  * @property boolean $restricted
+ * @property Collection $tags
+ * @method static Entity|Builder visible()
+ * @method static Entity|Builder hasPermission(string $permission)
+ * @method static Builder withLastView()
+ * @method static Builder withViewCount()
  *
  * @package BookStack\Entities
  */
@@ -40,14 +48,45 @@ class Entity extends Ownable
     public $searchFactor = 1.0;
 
     /**
-     * Get the morph class for this model.
-     * Set here since, due to folder changes, the namespace used
-     * in the database no longer matches the class namespace.
-     * @return string
+     * Get the entities that are visible to the current user.
+     */
+    public function scopeVisible(Builder $query)
+    {
+        return $this->scopeHasPermission($query, 'view');
+    }
+
+    /**
+     * Scope the query to those entities that the current user has the given permission for.
+     */
+    public function scopeHasPermission(Builder $query, string $permission)
+    {
+        return Permissions::restrictEntityQuery($query, $permission);
+    }
+
+    /**
+     * Query scope to get the last view from the current user.
      */
-    public function getMorphClass()
+    public function scopeWithLastView(Builder $query)
     {
-        return 'BookStack\\Entity';
+        $viewedAtQuery = View::query()->select('updated_at')
+            ->whereColumn('viewable_id', '=', $this->getTable() . '.id')
+            ->where('viewable_type', '=', $this->getMorphClass())
+            ->where('user_id', '=', user()->id)
+            ->take(1);
+
+        return $query->addSelect(['last_viewed_at' => $viewedAtQuery]);
+    }
+
+    /**
+     * Query scope to get the total view count of the entities.
+     */
+    public function scopeWithViewCount(Builder $query)
+    {
+        $viewCountQuery = View::query()->selectRaw('SUM(views) as view_count')
+            ->whereColumn('viewable_id', '=', $this->getTable() . '.id')
+            ->where('viewable_type', '=', $this->getMorphClass())->take(1);
+
+        $query->addSelect(['view_count' => $viewCountQuery]);
     }
 
     /**
@@ -87,11 +126,12 @@ class Entity extends Ownable
 
     /**
      * Gets the activity objects for this entity.
-     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
+     * @return MorphMany
      */
     public function activity()
     {
-        return $this->morphMany(Activity::class, 'entity')->orderBy('created_at', 'desc');
+        return $this->morphMany(Activity::class, 'entity')
+            ->orderBy('created_at', 'desc');
     }
 
     /**
@@ -102,14 +142,9 @@ class Entity extends Ownable
         return $this->morphMany(View::class, 'viewable');
     }
 
-    public function viewCountQuery()
-    {
-        return $this->views()->selectRaw('viewable_id, sum(views) as view_count')->groupBy('viewable_id');
-    }
-
     /**
      * Get the Tag models that have been user assigned to this entity.
-     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
+     * @return MorphMany
      */
     public function tags()
     {
@@ -129,7 +164,7 @@ class Entity extends Ownable
 
     /**
      * Get the related search terms.
-     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
+     * @return MorphMany
      */
     public function searchTerms()
     {
@@ -158,7 +193,7 @@ class Entity extends Ownable
 
     /**
      * Get the entity jointPermissions this is connected to.
-     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
+     * @return MorphMany
      */
     public function jointPermissions()
     {
@@ -238,21 +273,40 @@ class Entity extends Ownable
     }
 
     /**
-     * Return a generalised, common raw query that can be 'unioned' across entities.
+     * Get the url of this entity
+     * @param $path
      * @return string
      */
-    public function entityRawQuery()
+    public function getUrl($path = '/')
     {
-        return '';
+        return $path;
     }
 
     /**
-     * Get the url of this entity
-     * @param $path
-     * @return string
+     * Rebuild the permissions for this entity.
      */
-    public function getUrl($path = '/')
+    public function rebuildPermissions()
     {
-        return $path;
+        /** @noinspection PhpUnhandledExceptionInspection */
+        Permissions::buildJointPermissionsForEntity($this);
+    }
+
+    /**
+     * Index the current entity for search
+     */
+    public function indexForSearch()
+    {
+        $searchService = app()->make(SearchService::class);
+        $searchService->indexEntity($this);
+    }
+
+    /**
+     * Generate and set a new URL slug for this model.
+     */
+    public function refreshSlug(): string
+    {
+        $generator = new SlugGenerator($this);
+        $this->slug = $generator->generate();
+        return $this->slug;
     }
 }
index d0d4a7ad6c073a7737fdb0a03ef569fbef2487cb..6bf923b3112aa8e7387fd6eedeb601a601511dad 100644 (file)
@@ -39,11 +39,6 @@ class EntityProvider
 
     /**
      * EntityProvider constructor.
-     * @param Bookshelf $bookshelf
-     * @param Book $book
-     * @param Chapter $chapter
-     * @param Page $page
-     * @param PageRevision $pageRevision
      */
     public function __construct(
         Bookshelf $bookshelf,
@@ -62,9 +57,8 @@ class EntityProvider
     /**
      * Fetch all core entity types as an associated array
      * with their basic names as the keys.
-     * @return Entity[]
      */
-    public function all()
+    public function all(): array
     {
         return [
             'bookshelf' => $this->bookshelf,
@@ -76,10 +70,8 @@ class EntityProvider
 
     /**
      * Get an entity instance by it's basic name.
-     * @param string $type
-     * @return Entity
      */
-    public function get(string $type)
+    public function get(string $type): Entity
     {
         $type = strtolower($type);
         return $this->all()[$type];
@@ -87,15 +79,9 @@ class EntityProvider
 
     /**
      * Get the morph classes, as an array, for a single or multiple types.
-     * @param string|array $types
-     * @return array<string>
      */
-    public function getMorphClasses($types)
+    public function getMorphClasses(array $types): array
     {
-        if (is_string($types)) {
-            $types = [$types];
-        }
-
         $morphClasses = [];
         foreach ($types as $type) {
             $model = $this->get($type);
index 09635aa214f0b8e112583e2c747644030e5bd4b1..3ec867959a7d8b1046127e90534bff5050ca73f6 100644 (file)
@@ -1,35 +1,34 @@
 <?php namespace BookStack\Entities;
 
-use BookStack\Entities\Repos\EntityRepo;
+use BookStack\Entities\Managers\BookContents;
+use BookStack\Entities\Managers\PageContent;
 use BookStack\Uploads\ImageService;
+use DomPDF;
+use Exception;
+use SnappyPDF;
+use Throwable;
 
 class ExportService
 {
 
-    protected $entityRepo;
     protected $imageService;
 
     /**
      * ExportService constructor.
-     * @param EntityRepo $entityRepo
-     * @param ImageService $imageService
      */
-    public function __construct(EntityRepo $entityRepo, ImageService $imageService)
+    public function __construct(ImageService $imageService)
     {
-        $this->entityRepo = $entityRepo;
         $this->imageService = $imageService;
     }
 
     /**
      * Convert a page to a self-contained HTML file.
      * Includes required CSS & image content. Images are base64 encoded into the HTML.
-     * @param \BookStack\Entities\Page $page
-     * @return mixed|string
-     * @throws \Throwable
+     * @throws Throwable
      */
     public function pageToContainedHtml(Page $page)
     {
-        $this->entityRepo->renderPage($page);
+        $page->html = (new PageContent($page))->render();
         $pageHtml = view('pages/export', [
             'page' => $page
         ])->render();
@@ -38,15 +37,13 @@ class ExportService
 
     /**
      * Convert a chapter to a self-contained HTML file.
-     * @param \BookStack\Entities\Chapter $chapter
-     * @return mixed|string
-     * @throws \Throwable
+     * @throws Throwable
      */
     public function chapterToContainedHtml(Chapter $chapter)
     {
-        $pages = $this->entityRepo->getChapterChildren($chapter);
+        $pages = $chapter->getVisiblePages();
         $pages->each(function ($page) {
-            $page->html = $this->entityRepo->renderPage($page);
+            $page->html = (new PageContent($page))->render();
         });
         $html = view('chapters/export', [
             'chapter' => $chapter,
@@ -57,13 +54,11 @@ class ExportService
 
     /**
      * Convert a book to a self-contained HTML file.
-     * @param Book $book
-     * @return mixed|string
-     * @throws \Throwable
+     * @throws Throwable
      */
     public function bookToContainedHtml(Book $book)
     {
-        $bookTree = $this->entityRepo->getBookChildren($book, true, true);
+        $bookTree = (new BookContents($book))->getTree(false, true);
         $html = view('books/export', [
             'book' => $book,
             'bookChildren' => $bookTree
@@ -73,13 +68,11 @@ class ExportService
 
     /**
      * Convert a page to a PDF file.
-     * @param Page $page
-     * @return mixed|string
-     * @throws \Throwable
+     * @throws Throwable
      */
     public function pageToPdf(Page $page)
     {
-        $this->entityRepo->renderPage($page);
+        $page->html = (new PageContent($page))->render();
         $html = view('pages/pdf', [
             'page' => $page
         ])->render();
@@ -88,32 +81,30 @@ class ExportService
 
     /**
      * Convert a chapter to a PDF file.
-     * @param \BookStack\Entities\Chapter $chapter
-     * @return mixed|string
-     * @throws \Throwable
+     * @throws Throwable
      */
     public function chapterToPdf(Chapter $chapter)
     {
-        $pages = $this->entityRepo->getChapterChildren($chapter);
+        $pages = $chapter->getVisiblePages();
         $pages->each(function ($page) {
-            $page->html = $this->entityRepo->renderPage($page);
+            $page->html = (new PageContent($page))->render();
         });
+
         $html = view('chapters/export', [
             'chapter' => $chapter,
             'pages' => $pages
         ])->render();
+
         return $this->htmlToPdf($html);
     }
 
     /**
-     * Convert a book to a PDF file
-     * @param \BookStack\Entities\Book $book
-     * @return string
-     * @throws \Throwable
+     * Convert a book to a PDF file.
+     * @throws Throwable
      */
     public function bookToPdf(Book $book)
     {
-        $bookTree = $this->entityRepo->getBookChildren($book, true, true);
+        $bookTree = (new BookContents($book))->getTree(false, true);
         $html = view('books/export', [
             'book' => $book,
             'bookChildren' => $bookTree
@@ -122,31 +113,27 @@ class ExportService
     }
 
     /**
-     * Convert normal webpage HTML to a PDF.
-     * @param $html
-     * @return string
-     * @throws \Exception
+     * Convert normal web-page HTML to a PDF.
+     * @throws Exception
      */
-    protected function htmlToPdf($html)
+    protected function htmlToPdf(string $html): string
     {
         $containedHtml = $this->containHtml($html);
         $useWKHTML = config('snappy.pdf.binary') !== false;
         if ($useWKHTML) {
-            $pdf = \SnappyPDF::loadHTML($containedHtml);
+            $pdf = SnappyPDF::loadHTML($containedHtml);
             $pdf->setOption('print-media-type', true);
         } else {
-            $pdf = \DomPDF::loadHTML($containedHtml);
+            $pdf = DomPDF::loadHTML($containedHtml);
         }
         return $pdf->output();
     }
 
     /**
      * Bundle of the contents of a html file to be self-contained.
-     * @param $htmlContent
-     * @return mixed|string
-     * @throws \Exception
+     * @throws Exception
      */
-    protected function containHtml($htmlContent)
+    protected function containHtml(string $htmlContent): string
     {
         $imageTagsOutput = [];
         preg_match_all("/\<img.*src\=(\'|\")(.*?)(\'|\").*?\>/i", $htmlContent, $imageTagsOutput);
@@ -188,12 +175,10 @@ class ExportService
     /**
      * Converts the page contents into simple plain text.
      * This method filters any bad looking content to provide a nice final output.
-     * @param Page $page
-     * @return mixed
      */
-    public function pageToPlainText(Page $page)
+    public function pageToPlainText(Page $page): string
     {
-        $html = $this->entityRepo->renderPage($page);
+        $html = (new PageContent($page))->render();
         $text = strip_tags($html);
         // Replace multiple spaces with single spaces
         $text = preg_replace('/\ {2,}/', ' ', $text);
@@ -207,10 +192,8 @@ class ExportService
 
     /**
      * Convert a chapter into a plain text string.
-     * @param \BookStack\Entities\Chapter $chapter
-     * @return string
      */
-    public function chapterToPlainText(Chapter $chapter)
+    public function chapterToPlainText(Chapter $chapter): string
     {
         $text = $chapter->name . "\n\n";
         $text .= $chapter->description . "\n\n";
@@ -222,12 +205,10 @@ class ExportService
 
     /**
      * Convert a book into a plain text string.
-     * @param Book $book
-     * @return string
      */
-    public function bookToPlainText(Book $book)
+    public function bookToPlainText(Book $book): string
     {
-        $bookTree = $this->entityRepo->getBookChildren($book, true, true);
+        $bookTree = (new BookContents($book))->getTree(false, true);
         $text = $book->name . "\n\n";
         foreach ($bookTree as $bookChild) {
             if ($bookChild->isA('chapter')) {
diff --git a/app/Entities/HasCoverImage.php b/app/Entities/HasCoverImage.php
new file mode 100644 (file)
index 0000000..31277f4
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+
+namespace BookStack\Entities;
+
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+
+interface HasCoverImage
+{
+
+    /**
+     * Get the cover image for this item.
+     */
+    public function cover(): BelongsTo;
+
+    /**
+     * Get the type of the image model that is used when storing a cover image.
+     */
+    public function coverImageTypeKey(): string;
+}
diff --git a/app/Entities/Managers/BookContents.php b/app/Entities/Managers/BookContents.php
new file mode 100644 (file)
index 0000000..8b8d02c
--- /dev/null
@@ -0,0 +1,204 @@
+<?php namespace BookStack\Entities\Managers;
+
+use BookStack\Entities\Book;
+use BookStack\Entities\BookChild;
+use BookStack\Entities\Chapter;
+use BookStack\Entities\Entity;
+use BookStack\Entities\Page;
+use BookStack\Exceptions\SortOperationException;
+use Illuminate\Support\Collection;
+
+class BookContents
+{
+
+    /**
+     * @var Book
+     */
+    protected $book;
+
+    /**
+     * BookContents constructor.
+     * @param $book
+     */
+    public function __construct(Book $book)
+    {
+        $this->book = $book;
+    }
+
+    /**
+     * Get the current priority of the last item
+     * at the top-level of the book.
+     */
+    public function getLastPriority(): int
+    {
+        $maxPage = Page::visible()->where('book_id', '=', $this->book->id)
+            ->where('draft', '=', false)
+            ->where('chapter_id', '=', 0)->max('priority');
+        $maxChapter = Chapter::visible()->where('book_id', '=', $this->book->id)
+            ->max('priority');
+        return max($maxChapter, $maxPage, 1);
+    }
+
+    /**
+     * Get the contents as a sorted collection tree.
+     * TODO - Support $renderPages option
+     */
+    public function getTree(bool $showDrafts = false, bool $renderPages = false): Collection
+    {
+        $pages = $this->getPages($showDrafts);
+        $chapters = Chapter::visible()->where('book_id', '=', $this->book->id)->get();
+        $all = collect()->concat($pages)->concat($chapters);
+        $chapterMap = $chapters->keyBy('id');
+        $lonePages = collect();
+
+        $pages->groupBy('chapter_id')->each(function ($pages, $chapter_id) use ($chapterMap, &$lonePages) {
+            $chapter = $chapterMap->get($chapter_id);
+            if ($chapter) {
+                $chapter->setAttribute('pages', collect($pages)->sortBy($this->bookChildSortFunc()));
+            } else {
+                $lonePages = $lonePages->concat($pages);
+            }
+        });
+
+        $all->each(function (Entity $entity) {
+            $entity->setRelation('book', $this->book);
+        });
+
+        return collect($chapters)->concat($lonePages)->sortBy($this->bookChildSortFunc());
+    }
+
+    /**
+     * Function for providing a sorting score for an entity in relation to the
+     * other items within the book.
+     */
+    protected function bookChildSortFunc(): callable
+    {
+        return function (Entity $entity) {
+            if (isset($entity['draft']) && $entity['draft']) {
+                return -100;
+            }
+            return $entity['priority'] ?? 0;
+        };
+    }
+
+    /**
+     * Get the visible pages within this book.
+     */
+    protected function getPages(bool $showDrafts = false): Collection
+    {
+        $query = Page::visible()->where('book_id', '=', $this->book->id);
+
+        if (!$showDrafts) {
+            $query->where('draft', '=', false);
+        }
+
+        return $query->get();
+    }
+
+    /**
+     * Sort the books content using the given map.
+     * The map is a single-dimension collection of objects in the following format:
+     *   {
+     *     +"id": "294" (ID of item)
+     *     +"sort": 1 (Sort order index)
+     *     +"parentChapter": false (ID of parent chapter, as string, or false)
+     *     +"type": "page" (Entity type of item)
+     *     +"book": "1" (Id of book to place item in)
+     *   }
+     *
+     * Returns a list of books that were involved in the operation.
+     * @throws SortOperationException
+     */
+    public function sortUsingMap(Collection $sortMap): Collection
+    {
+        // Load models into map
+        $this->loadModelsIntoSortMap($sortMap);
+        $booksInvolved = $this->getBooksInvolvedInSort($sortMap);
+
+        // Perform the sort
+        $sortMap->each(function ($mapItem) {
+            $this->applySortUpdates($mapItem);
+        });
+
+        // Update permissions and activity.
+        $booksInvolved->each(function (Book $book) {
+            $book->rebuildPermissions();
+        });
+
+        return $booksInvolved;
+    }
+
+    /**
+     * Using the given sort map item, detect changes for the related model
+     * and update it if required.
+     */
+    protected function applySortUpdates(\stdClass $sortMapItem)
+    {
+        /** @var BookChild $model */
+        $model = $sortMapItem->model;
+
+        $priorityChanged = intval($model->priority) !== intval($sortMapItem->sort);
+        $bookChanged = intval($model->book_id) !== intval($sortMapItem->book);
+        $chapterChanged = ($sortMapItem->type === 'page') && intval($model->chapter_id) !== $sortMapItem->parentChapter;
+
+        if ($bookChanged) {
+            $model->changeBook($sortMapItem->book);
+        }
+
+        if ($chapterChanged) {
+            $model->chapter_id = intval($sortMapItem->parentChapter);
+            $model->save();
+        }
+
+        if ($priorityChanged) {
+            $model->priority = intval($sortMapItem->sort);
+            $model->save();
+        }
+    }
+
+    /**
+     * Load models from the database into the given sort map.
+     */
+    protected function loadModelsIntoSortMap(Collection $sortMap): void
+    {
+        $keyMap = $sortMap->keyBy(function (\stdClass $sortMapItem) {
+            return  $sortMapItem->type . ':' . $sortMapItem->id;
+        });
+        $pageIds = $sortMap->where('type', '=', 'page')->pluck('id');
+        $chapterIds = $sortMap->where('type', '=', 'chapter')->pluck('id');
+
+        $pages = Page::visible()->whereIn('id', $pageIds)->get();
+        $chapters = Chapter::visible()->whereIn('id', $chapterIds)->get();
+
+        foreach ($pages as $page) {
+            $sortItem = $keyMap->get('page:' . $page->id);
+            $sortItem->model = $page;
+        }
+
+        foreach ($chapters as $chapter) {
+            $sortItem = $keyMap->get('chapter:' . $chapter->id);
+            $sortItem->model = $chapter;
+        }
+    }
+
+    /**
+     * Get the books involved in a sort.
+     * The given sort map should have its models loaded first.
+     * @throws SortOperationException
+     */
+    protected function getBooksInvolvedInSort(Collection $sortMap): Collection
+    {
+        $bookIdsInvolved = collect([$this->book->id]);
+        $bookIdsInvolved = $bookIdsInvolved->concat($sortMap->pluck('book'));
+        $bookIdsInvolved = $bookIdsInvolved->concat($sortMap->pluck('model.book_id'));
+        $bookIdsInvolved = $bookIdsInvolved->unique()->toArray();
+
+        $books = Book::hasPermission('update')->whereIn('id', $bookIdsInvolved)->get();
+
+        if (count($books) !== count($bookIdsInvolved)) {
+            throw new SortOperationException("Could not find all books requested in sort operation");
+        }
+
+        return $books;
+    }
+}
similarity index 53%
rename from app/Entities/EntityContextManager.php
rename to app/Entities/Managers/EntityContext.php
index 20be0de2b01a5e34fc61a6d0b2752b6d8c2ae903..551cd1a100c142f26cea88d8e86aaa282c768fa0 100644 (file)
@@ -1,44 +1,38 @@
-<?php namespace BookStack\Entities;
+<?php namespace BookStack\Entities\Managers;
 
-use BookStack\Entities\Repos\EntityRepo;
+use BookStack\Entities\Book;
+use BookStack\Entities\Bookshelf;
 use Illuminate\Session\Store;
 
-class EntityContextManager
+class EntityContext
 {
     protected $session;
-    protected $entityRepo;
 
     protected $KEY_SHELF_CONTEXT_ID = 'context_bookshelf_id';
 
     /**
      * EntityContextManager constructor.
-     * @param Store $session
-     * @param EntityRepo $entityRepo
      */
-    public function __construct(Store $session, EntityRepo $entityRepo)
+    public function __construct(Store $session)
     {
         $this->session = $session;
-        $this->entityRepo = $entityRepo;
     }
 
     /**
      * Get the current bookshelf context for the given book.
-     * @param Book $book
-     * @return Bookshelf|null
      */
-    public function getContextualShelfForBook(Book $book)
+    public function getContextualShelfForBook(Book $book): ?Bookshelf
     {
         $contextBookshelfId = $this->session->get($this->KEY_SHELF_CONTEXT_ID, null);
-        if (is_int($contextBookshelfId)) {
 
-            /** @var Bookshelf $shelf */
-            $shelf = $this->entityRepo->getById('bookshelf', $contextBookshelfId);
-
-            if ($shelf && $shelf->contains($book)) {
-                return $shelf;
-            }
+        if (!is_int($contextBookshelfId)) {
+            return null;
         }
-        return null;
+
+        $shelf = Bookshelf::visible()->find($contextBookshelfId);
+        $shelfContainsBook = $shelf && $shelf->contains($book);
+
+        return $shelfContainsBook ? $shelf : null;
     }
 
     /**
diff --git a/app/Entities/Managers/PageContent.php b/app/Entities/Managers/PageContent.php
new file mode 100644 (file)
index 0000000..36bc244
--- /dev/null
@@ -0,0 +1,304 @@
+<?php namespace BookStack\Entities\Managers;
+
+use BookStack\Entities\Page;
+use DOMDocument;
+use DOMElement;
+use DOMNodeList;
+use DOMXPath;
+
+class PageContent
+{
+
+    protected $page;
+
+    /**
+     * PageContent constructor.
+     */
+    public function __construct(Page $page)
+    {
+        $this->page = $page;
+    }
+
+    /**
+     * Update the content of the page with new provided HTML.
+     */
+    public function setNewHTML(string $html)
+    {
+        $this->page->html = $this->formatHtml($html);
+        $this->page->text = $this->toPlainText();
+    }
+
+    /**
+     * Formats a page's html to be tagged correctly within the system.
+     */
+    protected function formatHtml(string $htmlText): string
+    {
+        if ($htmlText == '') {
+            return $htmlText;
+        }
+
+        libxml_use_internal_errors(true);
+        $doc = new DOMDocument();
+        $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
+
+        $container = $doc->documentElement;
+        $body = $container->childNodes->item(0);
+        $childNodes = $body->childNodes;
+
+        // Set ids on top-level nodes
+        $idMap = [];
+        foreach ($childNodes as $index => $childNode) {
+            $this->setUniqueId($childNode, $idMap);
+        }
+
+        // Ensure no duplicate ids within child items
+        $xPath = new DOMXPath($doc);
+        $idElems = $xPath->query('//body//*//*[@id]');
+        foreach ($idElems as $domElem) {
+            $this->setUniqueId($domElem, $idMap);
+        }
+
+        // Generate inner html as a string
+        $html = '';
+        foreach ($childNodes as $childNode) {
+            $html .= $doc->saveHTML($childNode);
+        }
+
+        return $html;
+    }
+
+    /**
+     * Set a unique id on the given DOMElement.
+     * A map for existing ID's should be passed in to check for current existence.
+     * @param DOMElement $element
+     * @param array $idMap
+     */
+    protected function setUniqueId($element, array &$idMap)
+    {
+        if (get_class($element) !== 'DOMElement') {
+            return;
+        }
+
+        // Overwrite id if not a BookStack custom id
+        $existingId = $element->getAttribute('id');
+        if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) {
+            $idMap[$existingId] = true;
+            return;
+        }
+
+        // Create an unique id for the element
+        // Uses the content as a basis to ensure output is the same every time
+        // the same content is passed through.
+        $contentId = 'bkmrk-' . mb_substr(strtolower(preg_replace('/\s+/', '-', trim($element->nodeValue))), 0, 20);
+        $newId = urlencode($contentId);
+        $loopIndex = 0;
+
+        while (isset($idMap[$newId])) {
+            $newId = urlencode($contentId . '-' . $loopIndex);
+            $loopIndex++;
+        }
+
+        $element->setAttribute('id', $newId);
+        $idMap[$newId] = true;
+    }
+
+    /**
+     * Get a plain-text visualisation of this page.
+     */
+    protected function toPlainText(): string
+    {
+        $html = $this->render(true);
+        return strip_tags($html);
+    }
+
+    /**
+     * Render the page for viewing
+     */
+    public function render(bool $blankIncludes = false) : string
+    {
+        $content = $this->page->html;
+
+        if (!config('app.allow_content_scripts')) {
+            $content = $this->escapeScripts($content);
+        }
+
+        if ($blankIncludes) {
+            $content = $this->blankPageIncludes($content);
+        } else {
+            $content = $this->parsePageIncludes($content);
+        }
+
+        return $content;
+    }
+
+    /**
+     * Parse the headers on the page to get a navigation menu
+     */
+    public function getNavigation(string $htmlContent): array
+    {
+        if (empty($htmlContent)) {
+            return [];
+        }
+
+        libxml_use_internal_errors(true);
+        $doc = new DOMDocument();
+        $doc->loadHTML(mb_convert_encoding($htmlContent, 'HTML-ENTITIES', 'UTF-8'));
+        $xPath = new DOMXPath($doc);
+        $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
+
+        return $headers ? $this->headerNodesToLevelList($headers) : [];
+    }
+
+    /**
+     * Convert a DOMNodeList into an array of readable header attributes
+     * with levels normalised to the lower header level.
+     */
+    protected function headerNodesToLevelList(DOMNodeList $nodeList): array
+    {
+        $tree = collect($nodeList)->map(function ($header) {
+            $text = trim(str_replace("\xc2\xa0", '', $header->nodeValue));
+            $text = mb_substr($text, 0, 100);
+
+            return [
+                'nodeName' => strtolower($header->nodeName),
+                'level' => intval(str_replace('h', '', $header->nodeName)),
+                'link' => '#' . $header->getAttribute('id'),
+                'text' => $text,
+            ];
+        })->filter(function ($header) {
+            return mb_strlen($header['text']) > 0;
+        });
+
+        // Shift headers if only smaller headers have been used
+        $levelChange = ($tree->pluck('level')->min() - 1);
+        $tree = $tree->map(function ($header) use ($levelChange) {
+            $header['level'] -= ($levelChange);
+            return $header;
+        });
+
+        return $tree->toArray();
+    }
+
+    /**
+     * Remove any page include tags within the given HTML.
+     */
+    protected function blankPageIncludes(string $html) : string
+    {
+        return preg_replace("/{{@\s?([0-9].*?)}}/", '', $html);
+    }
+
+    /**
+     * Parse any include tags "{{@<page_id>#section}}" to be part of the page.
+     */
+    protected function parsePageIncludes(string $html) : string
+    {
+        $matches = [];
+        preg_match_all("/{{@\s?([0-9].*?)}}/", $html, $matches);
+
+        foreach ($matches[1] as $index => $includeId) {
+            $fullMatch = $matches[0][$index];
+            $splitInclude = explode('#', $includeId, 2);
+
+            // Get page id from reference
+            $pageId = intval($splitInclude[0]);
+            if (is_nan($pageId)) {
+                continue;
+            }
+
+            // Find page and skip this if page not found
+            $matchedPage = Page::visible()->find($pageId);
+            if ($matchedPage === null) {
+                $html = str_replace($fullMatch, '', $html);
+                continue;
+            }
+
+            // If we only have page id, just insert all page html and continue.
+            if (count($splitInclude) === 1) {
+                $html = str_replace($fullMatch, $matchedPage->html, $html);
+                continue;
+            }
+
+            // Create and load HTML into a document
+            $innerContent = $this->fetchSectionOfPage($matchedPage, $splitInclude[1]);
+            $html = str_replace($fullMatch, trim($innerContent), $html);
+        }
+
+        return $html;
+    }
+
+
+    /**
+     * Fetch the content from a specific section of the given page.
+     */
+    protected function fetchSectionOfPage(Page $page, string $sectionId): string
+    {
+        $topLevelTags = ['table', 'ul', 'ol'];
+        $doc = new DOMDocument();
+        libxml_use_internal_errors(true);
+        $doc->loadHTML(mb_convert_encoding('<body>'.$page->html.'</body>', 'HTML-ENTITIES', 'UTF-8'));
+
+        // Search included content for the id given and blank out if not exists.
+        $matchingElem = $doc->getElementById($sectionId);
+        if ($matchingElem === null) {
+            return '';
+        }
+
+        // Otherwise replace the content with the found content
+        // Checks if the top-level wrapper should be included by matching on tag types
+        $innerContent = '';
+        $isTopLevel = in_array(strtolower($matchingElem->nodeName), $topLevelTags);
+        if ($isTopLevel) {
+            $innerContent .= $doc->saveHTML($matchingElem);
+        } else {
+            foreach ($matchingElem->childNodes as $childNode) {
+                $innerContent .= $doc->saveHTML($childNode);
+            }
+        }
+        libxml_clear_errors();
+
+        return $innerContent;
+    }
+
+    /**
+     * Escape script tags within HTML content.
+     */
+    protected function escapeScripts(string $html) : string
+    {
+        if (empty($html)) {
+            return $html;
+        }
+
+        libxml_use_internal_errors(true);
+        $doc = new DOMDocument();
+        $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
+        $xPath = new DOMXPath($doc);
+
+        // Remove standard script tags
+        $scriptElems = $xPath->query('//script');
+        foreach ($scriptElems as $scriptElem) {
+            $scriptElem->parentNode->removeChild($scriptElem);
+        }
+
+        // Remove data or JavaScript iFrames
+        $badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')] | //*[@srcdoc]');
+        foreach ($badIframes as $badIframe) {
+            $badIframe->parentNode->removeChild($badIframe);
+        }
+
+        // Remove 'on*' attributes
+        $onAttributes = $xPath->query('//@*[starts-with(name(), \'on\')]');
+        foreach ($onAttributes as $attr) {
+            /** @var \DOMAttr $attr*/
+            $attrName = $attr->nodeName;
+            $attr->parentNode->removeAttribute($attrName);
+        }
+
+        $html = '';
+        $topElems = $doc->documentElement->childNodes->item(0)->childNodes;
+        foreach ($topElems as $child) {
+            $html .= $doc->saveHTML($child);
+        }
+
+        return $html;
+    }
+}
diff --git a/app/Entities/Managers/PageEditActivity.php b/app/Entities/Managers/PageEditActivity.php
new file mode 100644 (file)
index 0000000..cebbf87
--- /dev/null
@@ -0,0 +1,74 @@
+<?php namespace BookStack\Entities\Managers;
+
+use BookStack\Entities\Page;
+use BookStack\Entities\PageRevision;
+use Carbon\Carbon;
+use Illuminate\Database\Eloquent\Builder;
+
+class PageEditActivity
+{
+
+    protected $page;
+
+    /**
+     * PageEditActivity constructor.
+     */
+    public function __construct(Page $page)
+    {
+        $this->page = $page;
+    }
+
+    /**
+     * Check if there's active editing being performed on this page.
+     * @return bool
+     */
+    public function hasActiveEditing(): bool
+    {
+        return $this->activePageEditingQuery(60)->count() > 0;
+    }
+
+    /**
+     * Get a notification message concerning the editing activity on the page.
+     */
+    public function activeEditingMessage(): string
+    {
+        $pageDraftEdits = $this->activePageEditingQuery(60)->get();
+        $count = $pageDraftEdits->count();
+
+        $userMessage = $count > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $count]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
+        $timeMessage = trans('entities.pages_draft_edit_active.time_b', ['minCount'=> 60]);
+        return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]);
+    }
+
+    /**
+     * Get the message to show when the user will be editing one of their drafts.
+     * @param PageRevision $draft
+     * @return string
+     */
+    public function getEditingActiveDraftMessage(PageRevision $draft): string
+    {
+        $message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]);
+        if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) {
+            return $message;
+        }
+        return $message . "\n" . trans('entities.pages_draft_edited_notification');
+    }
+
+    /**
+     * A query to check for active update drafts on a particular page
+     * within the last given many minutes.
+     */
+    protected function activePageEditingQuery(int $withinMinutes): Builder
+    {
+        $checkTime = Carbon::now()->subMinutes($withinMinutes);
+        $query = PageRevision::query()
+            ->where('type', '=', 'update_draft')
+            ->where('page_id', '=', $this->page->id)
+            ->where('updated_at', '>', $this->page->updated_at)
+            ->where('created_by', '!=', user()->id)
+            ->where('updated_at', '>=', $checkTime)
+            ->with('createdBy');
+
+        return $query;
+    }
+}
diff --git a/app/Entities/Managers/TrashCan.php b/app/Entities/Managers/TrashCan.php
new file mode 100644 (file)
index 0000000..1a32294
--- /dev/null
@@ -0,0 +1,109 @@
+<?php namespace BookStack\Entities\Managers;
+
+use BookStack\Entities\Book;
+use BookStack\Entities\Bookshelf;
+use BookStack\Entities\Chapter;
+use BookStack\Entities\Entity;
+use BookStack\Entities\HasCoverImage;
+use BookStack\Entities\Page;
+use BookStack\Exceptions\NotifyException;
+use BookStack\Facades\Activity;
+use BookStack\Uploads\AttachmentService;
+use BookStack\Uploads\ImageService;
+use Exception;
+use Illuminate\Contracts\Container\BindingResolutionException;
+
+class TrashCan
+{
+
+    /**
+     * Remove a bookshelf from the system.
+     * @throws Exception
+     */
+    public function destroyShelf(Bookshelf $shelf)
+    {
+        $this->destroyCommonRelations($shelf);
+        $shelf->delete();
+    }
+
+    /**
+     * Remove a book from the system.
+     * @throws NotifyException
+     * @throws BindingResolutionException
+     */
+    public function destroyBook(Book $book)
+    {
+        foreach ($book->pages as $page) {
+            $this->destroyPage($page);
+        }
+
+        foreach ($book->chapters as $chapter) {
+            $this->destroyChapter($chapter);
+        }
+
+        $this->destroyCommonRelations($book);
+        $book->delete();
+    }
+
+    /**
+     * Remove a page from the system.
+     * @throws NotifyException
+     */
+    public function destroyPage(Page $page)
+    {
+        // Check if set as custom homepage & remove setting if not used or throw error if active
+        $customHome = setting('app-homepage', '0:');
+        if (intval($page->id) === intval(explode(':', $customHome)[0])) {
+            if (setting('app-homepage-type') === 'page') {
+                throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl());
+            }
+            setting()->remove('app-homepage');
+        }
+
+        $this->destroyCommonRelations($page);
+
+        // Delete Attached Files
+        $attachmentService = app(AttachmentService::class);
+        foreach ($page->attachments as $attachment) {
+            $attachmentService->deleteFile($attachment);
+        }
+
+        $page->delete();
+    }
+
+    /**
+     * Remove a chapter from the system.
+     * @throws Exception
+     */
+    public function destroyChapter(Chapter $chapter)
+    {
+        if (count($chapter->pages) > 0) {
+            foreach ($chapter->pages as $page) {
+                $page->chapter_id = 0;
+                $page->save();
+            }
+        }
+
+        $this->destroyCommonRelations($chapter);
+        $chapter->delete();
+    }
+
+    /**
+     * Update entity relations to remove or update outstanding connections.
+     */
+    protected function destroyCommonRelations(Entity $entity)
+    {
+        Activity::removeEntity($entity);
+        $entity->views()->delete();
+        $entity->permissions()->delete();
+        $entity->tags()->delete();
+        $entity->comments()->delete();
+        $entity->jointPermissions()->delete();
+        $entity->searchTerms()->delete();
+
+        if ($entity instanceof HasCoverImage && $entity->cover) {
+            $imageService = app()->make(ImageService::class);
+            $imageService->destroy($entity->cover);
+        }
+    }
+}
index c32417418b7c0a2f0cd2e61f8b4856af17c476c5..76dc628fbf3f59e0bbbcf98897e715b743638213 100644 (file)
@@ -1,8 +1,25 @@
 <?php namespace BookStack\Entities;
 
 use BookStack\Uploads\Attachment;
-
-class Page extends Entity
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\HasMany;
+use Permissions;
+
+/**
+ * Class Page
+ * @property int $chapter_id
+ * @property string $html
+ * @property string $markdown
+ * @property string $text
+ * @property bool $template
+ * @property bool $draft
+ * @property int $revision_count
+ * @property Chapter $chapter
+ * @property Collection $attachments
+ */
+class Page extends BookChild
 {
     protected $fillable = ['name', 'html', 'priority', 'markdown'];
 
@@ -11,12 +28,12 @@ class Page extends Entity
     public $textField = 'text';
 
     /**
-     * Get the morph class for this model.
-     * @return string
+     * Get the entities that are visible to the current user.
      */
-    public function getMorphClass()
+    public function scopeVisible(Builder $query)
     {
-        return 'BookStack\\Page';
+        $query = Permissions::enforceDraftVisiblityOnQuery($query);
+        return parent::scopeVisible($query);
     }
 
     /**
@@ -30,27 +47,17 @@ class Page extends Entity
         return $array;
     }
 
-    /**
-     * Get the book this page sits in.
-     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
-     */
-    public function book()
-    {
-        return $this->belongsTo(Book::class);
-    }
-
     /**
      * Get the parent item
-     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
      */
-    public function parent()
+    public function parent(): Entity
     {
-        return $this->chapter_id ? $this->chapter() : $this->book();
+        return $this->chapter_id ? $this->chapter : $this->book;
     }
 
     /**
      * Get the chapter that this page is in, If applicable.
-     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+     * @return BelongsTo
      */
     public function chapter()
     {
@@ -72,12 +79,12 @@ class Page extends Entity
      */
     public function revisions()
     {
-        return $this->hasMany(PageRevision::class)->where('type', '=', 'version')->orderBy('created_at', 'desc');
+        return $this->hasMany(PageRevision::class)->where('type', '=', 'version')->orderBy('created_at', 'desc')->orderBy('id', 'desc');
     }
 
     /**
      * Get the attachments assigned to this page.
-     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     * @return HasMany
      */
     public function attachments()
     {
@@ -95,27 +102,17 @@ class Page extends Entity
         $midText = $this->draft ? '/draft/' : '/page/';
         $idComponent = $this->draft ? $this->id : urlencode($this->slug);
 
+        $url = '/books/' . urlencode($bookSlug) . $midText . $idComponent;
         if ($path !== false) {
-            return url('/books/' . urlencode($bookSlug) . $midText . $idComponent . '/' . trim($path, '/'));
+            $url .= '/' . trim($path, '/');
         }
 
-        return url('/books/' . urlencode($bookSlug) . $midText . $idComponent);
-    }
-
-    /**
-     * Return a generalised, common raw query that can be 'unioned' across entities.
-     * @param bool $withContent
-     * @return string
-     */
-    public function entityRawQuery($withContent = false)
-    {
-        $htmlQuery = $withContent ? 'html' : "'' as html";
-        return "'BookStack\\\\Page' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text, {$htmlQuery}, book_id, priority, chapter_id, draft, created_by, updated_by, updated_at, created_at";
+        return url($url);
     }
 
     /**
      * Get the current revision for the page if existing
-     * @return \BookStack\Entities\PageRevision|null
+     * @return PageRevision|null
      */
     public function getCurrentRevision()
     {
index d30147bfc52b1789f2faf4ecf89d8e3b50345c09..13dc713ba43be37453ca525f06e91f143793476e 100644 (file)
@@ -2,7 +2,21 @@
 
 use BookStack\Auth\User;
 use BookStack\Model;
+use Carbon\Carbon;
 
+/**
+ * Class PageRevision
+ * @property int $page_id
+ * @property string $slug
+ * @property string $book_slug
+ * @property int $created_by
+ * @property Carbon $created_at
+ * @property string $type
+ * @property string $summary
+ * @property string $markdown
+ * @property string $html
+ * @property int $revision_number
+ */
 class PageRevision extends Model
 {
     protected $fillable = ['name', 'html', 'text', 'markdown', 'summary'];
@@ -41,13 +55,18 @@ class PageRevision extends Model
 
     /**
      * Get the previous revision for the same page if existing
-     * @return \BookStack\PageRevision|null
+     * @return \BookStack\Entities\PageRevision|null
      */
     public function getPrevious()
     {
-        if ($id = static::where('page_id', '=', $this->page_id)->where('id', '<', $this->id)->max('id')) {
-            return static::find($id);
+        $id = static::newQuery()->where('page_id', '=', $this->page_id)
+            ->where('id', '<', $this->id)
+            ->max('id');
+
+        if ($id) {
+            return static::query()->find($id);
         }
+
         return null;
     }
 
diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php
new file mode 100644 (file)
index 0000000..23f07f8
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+
+namespace BookStack\Entities\Repos;
+
+use BookStack\Actions\TagRepo;
+use BookStack\Entities\Book;
+use BookStack\Entities\Entity;
+use BookStack\Entities\HasCoverImage;
+use BookStack\Exceptions\ImageUploadException;
+use BookStack\Uploads\ImageRepo;
+use Illuminate\Http\UploadedFile;
+use Illuminate\Support\Collection;
+
+class BaseRepo
+{
+
+    protected $tagRepo;
+    protected $imageRepo;
+
+
+    /**
+     * BaseRepo constructor.
+     * @param $tagRepo
+     */
+    public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo)
+    {
+        $this->tagRepo = $tagRepo;
+        $this->imageRepo = $imageRepo;
+    }
+
+    /**
+     * Create a new entity in the system
+     */
+    public function create(Entity $entity, array $input)
+    {
+        $entity->fill($input);
+        $entity->forceFill([
+            'created_by' => user()->id,
+            'updated_by' => user()->id,
+        ]);
+        $entity->refreshSlug();
+        $entity->save();
+
+        if (isset($input['tags'])) {
+            $this->tagRepo->saveTagsToEntity($entity, $input['tags']);
+        }
+
+        $entity->rebuildPermissions();
+        $entity->indexForSearch();
+    }
+
+    /**
+     * Update the given entity.
+     */
+    public function update(Entity $entity, array $input)
+    {
+        $entity->fill($input);
+        $entity->updated_by = user()->id;
+
+        if ($entity->isDirty('name')) {
+            $entity->refreshSlug();
+        }
+
+        $entity->save();
+
+        if (isset($input['tags'])) {
+            $this->tagRepo->saveTagsToEntity($entity, $input['tags']);
+        }
+
+        $entity->rebuildPermissions();
+        $entity->indexForSearch();
+    }
+
+    /**
+     * Update the given items' cover image, or clear it.
+     * @throws ImageUploadException
+     * @throws \Exception
+     */
+    public function updateCoverImage(HasCoverImage $entity, UploadedFile $coverImage = null, bool $removeImage = false)
+    {
+        if ($coverImage) {
+            $this->imageRepo->destroyImage($entity->cover);
+            $image = $this->imageRepo->saveNew($coverImage, 'cover_book', $entity->id, 512, 512, true);
+            $entity->cover()->associate($image);
+            $entity->save();
+        }
+
+        if ($removeImage) {
+            $this->imageRepo->destroyImage($entity->cover);
+            $entity->image_id = 0;
+            $entity->save();
+        }
+    }
+
+    /**
+     * Update the permissions of an entity.
+     */
+    public function updatePermissions(Entity $entity, bool $restricted, Collection $permissions = null)
+    {
+        $entity->restricted = $restricted;
+        $entity->permissions()->delete();
+
+        if (!is_null($permissions)) {
+            $entityPermissionData = $permissions->flatMap(function ($restrictions, $roleId) {
+                return collect($restrictions)->keys()->map(function ($action) use ($roleId) {
+                    return [
+                        'role_id' => $roleId,
+                        'action' => strtolower($action),
+                    ] ;
+                });
+            });
+
+            $entity->permissions()->createMany($entityPermissionData);
+        }
+
+        $entity->save();
+        $entity->rebuildPermissions();
+    }
+}
diff --git a/app/Entities/Repos/BookRepo.php b/app/Entities/Repos/BookRepo.php
new file mode 100644 (file)
index 0000000..7fcc80f
--- /dev/null
@@ -0,0 +1,134 @@
+<?php namespace BookStack\Entities\Repos;
+
+use BookStack\Actions\TagRepo;
+use BookStack\Entities\Book;
+use BookStack\Entities\Managers\TrashCan;
+use BookStack\Exceptions\ImageUploadException;
+use BookStack\Exceptions\NotFoundException;
+use BookStack\Exceptions\NotifyException;
+use BookStack\Uploads\ImageRepo;
+use Exception;
+use Illuminate\Contracts\Container\BindingResolutionException;
+use Illuminate\Contracts\Pagination\LengthAwarePaginator;
+use Illuminate\Http\UploadedFile;
+use Illuminate\Support\Collection;
+
+class BookRepo
+{
+
+    protected $baseRepo;
+    protected $tagRepo;
+    protected $imageRepo;
+
+    /**
+     * BookRepo constructor.
+     * @param $tagRepo
+     */
+    public function __construct(BaseRepo $baseRepo, TagRepo $tagRepo, ImageRepo $imageRepo)
+    {
+        $this->baseRepo = $baseRepo;
+        $this->tagRepo = $tagRepo;
+        $this->imageRepo = $imageRepo;
+    }
+
+    /**
+     * Get all books in a paginated format.
+     */
+    public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator
+    {
+        return Book::visible()->orderBy($sort, $order)->paginate($count);
+    }
+
+    /**
+     * Get the books that were most recently viewed by this user.
+     */
+    public function getRecentlyViewed(int $count = 20): Collection
+    {
+        return Book::visible()->withLastView()
+            ->having('last_viewed_at', '>', 0)
+            ->orderBy('last_viewed_at', 'desc')
+            ->take($count)->get();
+    }
+
+    /**
+     * Get the most popular books in the system.
+     */
+    public function getPopular(int $count = 20): Collection
+    {
+        return Book::visible()->withViewCount()
+            ->having('view_count', '>', 0)
+            ->orderBy('view_count', 'desc')
+            ->take($count)->get();
+    }
+
+    /**
+     * Get the most recently created books from the system.
+     */
+    public function getRecentlyCreated(int $count = 20): Collection
+    {
+        return Book::visible()->orderBy('created_at', 'desc')
+            ->take($count)->get();
+    }
+
+    /**
+     * Get a book by its slug.
+     */
+    public function getBySlug(string $slug): Book
+    {
+        $book = Book::visible()->where('slug', '=', $slug)->first();
+
+        if ($book === null) {
+            throw new NotFoundException(trans('errors.book_not_found'));
+        }
+
+        return $book;
+    }
+
+    /**
+     * Create a new book in the system
+     */
+    public function create(array $input): Book
+    {
+        $book = new Book();
+        $this->baseRepo->create($book, $input);
+        return $book;
+    }
+
+    /**
+     * Update the given book.
+     */
+    public function update(Book $book, array $input): Book
+    {
+        $this->baseRepo->update($book, $input);
+        return $book;
+    }
+
+    /**
+     * Update the given book's cover image, or clear it.
+     * @throws ImageUploadException
+     * @throws Exception
+     */
+    public function updateCoverImage(Book $book, UploadedFile $coverImage = null, bool $removeImage = false)
+    {
+        $this->baseRepo->updateCoverImage($book, $coverImage, $removeImage);
+    }
+
+    /**
+     * Update the permissions of a book.
+     */
+    public function updatePermissions(Book $book, bool $restricted, Collection $permissions = null)
+    {
+        $this->baseRepo->updatePermissions($book, $restricted, $permissions);
+    }
+
+    /**
+     * Remove a book from the system.
+     * @throws NotifyException
+     * @throws BindingResolutionException
+     */
+    public function destroy(Book $book)
+    {
+        $trashCan = new TrashCan();
+        $trashCan->destroyBook($book);
+    }
+}
diff --git a/app/Entities/Repos/BookshelfRepo.php b/app/Entities/Repos/BookshelfRepo.php
new file mode 100644 (file)
index 0000000..ab4a518
--- /dev/null
@@ -0,0 +1,173 @@
+<?php namespace BookStack\Entities\Repos;
+
+use BookStack\Entities\Book;
+use BookStack\Entities\Bookshelf;
+use BookStack\Entities\Managers\TrashCan;
+use BookStack\Exceptions\ImageUploadException;
+use BookStack\Exceptions\NotFoundException;
+use Exception;
+use Illuminate\Contracts\Pagination\LengthAwarePaginator;
+use Illuminate\Http\UploadedFile;
+use Illuminate\Support\Collection;
+
+class BookshelfRepo
+{
+    protected $baseRepo;
+
+    /**
+     * BookshelfRepo constructor.
+     * @param $baseRepo
+     */
+    public function __construct(BaseRepo $baseRepo)
+    {
+        $this->baseRepo = $baseRepo;
+    }
+
+    /**
+     * Get all bookshelves in a paginated format.
+     */
+    public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator
+    {
+        return Bookshelf::visible()->with('visibleBooks')
+            ->orderBy($sort, $order)->paginate($count);
+    }
+
+    /**
+     * Get the bookshelves that were most recently viewed by this user.
+     */
+    public function getRecentlyViewed(int $count = 20): Collection
+    {
+        return Bookshelf::visible()->withLastView()
+            ->having('last_viewed_at', '>', 0)
+            ->orderBy('last_viewed_at', 'desc')
+            ->take($count)->get();
+    }
+
+    /**
+     * Get the most popular bookshelves in the system.
+     */
+    public function getPopular(int $count = 20): Collection
+    {
+        return Bookshelf::visible()->withViewCount()
+            ->having('view_count', '>', 0)
+            ->orderBy('view_count', 'desc')
+            ->take($count)->get();
+    }
+
+    /**
+     * Get the most recently created bookshelves from the system.
+     */
+    public function getRecentlyCreated(int $count = 20): Collection
+    {
+        return Bookshelf::visible()->orderBy('created_at', 'desc')
+            ->take($count)->get();
+    }
+
+    /**
+     * Get a shelf by its slug.
+     */
+    public function getBySlug(string $slug): Bookshelf
+    {
+        $shelf = Bookshelf::visible()->where('slug', '=', $slug)->first();
+
+        if ($shelf === null) {
+            throw new NotFoundException(trans('errors.bookshelf_not_found'));
+        }
+
+        return $shelf;
+    }
+
+    /**
+     * Create a new shelf in the system.
+     */
+    public function create(array $input, array $bookIds): Bookshelf
+    {
+        $shelf = new Bookshelf();
+        $this->baseRepo->create($shelf, $input);
+        $this->updateBooks($shelf, $bookIds);
+        return $shelf;
+    }
+
+    /**
+     * Create a new shelf in the system.
+     */
+    public function update(Bookshelf $shelf, array $input, array $bookIds): Bookshelf
+    {
+        $this->baseRepo->update($shelf, $input);
+        $this->updateBooks($shelf, $bookIds);
+        return $shelf;
+    }
+
+    /**
+     * Update which books are assigned to this shelf by
+     * syncing the given book ids.
+     * Function ensures the books are visible to the current user and existing.
+     */
+    protected function updateBooks(Bookshelf $shelf, array $bookIds)
+    {
+        $numericIDs = collect($bookIds)->map(function ($id) {
+            return intval($id);
+        });
+
+        $syncData = Book::visible()
+            ->whereIn('id', $bookIds)
+            ->get(['id'])->pluck('id')->mapWithKeys(function ($bookId) use ($numericIDs) {
+                return [$bookId => ['order' => $numericIDs->search($bookId)]];
+            });
+
+        $shelf->books()->sync($syncData);
+    }
+
+    /**
+     * Update the given shelf cover image, or clear it.
+     * @throws ImageUploadException
+     * @throws Exception
+     */
+    public function updateCoverImage(Bookshelf $shelf, UploadedFile $coverImage = null, bool $removeImage = false)
+    {
+        $this->baseRepo->updateCoverImage($shelf, $coverImage, $removeImage);
+    }
+
+    /**
+     * Update the permissions of a bookshelf.
+     */
+    public function updatePermissions(Bookshelf $shelf, bool $restricted, Collection $permissions = null)
+    {
+        $this->baseRepo->updatePermissions($shelf, $restricted, $permissions);
+    }
+
+    /**
+     * Copy down the permissions of the given shelf to all child books.
+     */
+    public function copyDownPermissions(Bookshelf $shelf): int
+    {
+        $shelfPermissions = $shelf->permissions()->get(['role_id', 'action'])->toArray();
+        $shelfBooks = $shelf->books()->get();
+        $updatedBookCount = 0;
+
+        /** @var Book $book */
+        foreach ($shelfBooks as $book) {
+            if (!userCan('restrictions-manage', $book)) {
+                continue;
+            }
+            $book->permissions()->delete();
+            $book->restricted = $shelf->restricted;
+            $book->permissions()->createMany($shelfPermissions);
+            $book->save();
+            $book->rebuildPermissions();
+            $updatedBookCount++;
+        }
+
+        return $updatedBookCount;
+    }
+
+    /**
+     * Remove a bookshelf from the system.
+     * @throws Exception
+     */
+    public function destroy(Bookshelf $shelf)
+    {
+        $trashCan = new TrashCan();
+        $trashCan->destroyShelf($shelf);
+    }
+}
diff --git a/app/Entities/Repos/ChapterRepo.php b/app/Entities/Repos/ChapterRepo.php
new file mode 100644 (file)
index 0000000..c6f3a2d
--- /dev/null
@@ -0,0 +1,108 @@
+<?php namespace BookStack\Entities\Repos;
+
+use BookStack\Entities\Book;
+use BookStack\Entities\Chapter;
+use BookStack\Entities\Managers\BookContents;
+use BookStack\Entities\Managers\TrashCan;
+use BookStack\Exceptions\MoveOperationException;
+use BookStack\Exceptions\NotFoundException;
+use BookStack\Exceptions\NotifyException;
+use Exception;
+use Illuminate\Contracts\Container\BindingResolutionException;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Collection;
+
+class ChapterRepo
+{
+
+    protected $baseRepo;
+
+    /**
+     * ChapterRepo constructor.
+     * @param $baseRepo
+     */
+    public function __construct(BaseRepo $baseRepo)
+    {
+        $this->baseRepo = $baseRepo;
+    }
+
+    /**
+     * Get a chapter via the slug.
+     * @throws NotFoundException
+     */
+    public function getBySlug(string $bookSlug, string $chapterSlug): Chapter
+    {
+        $chapter = Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->first();
+
+        if ($chapter === null) {
+            throw new NotFoundException(trans('errors.chapter_not_found'));
+        }
+
+        return $chapter;
+    }
+
+    /**
+     * Create a new chapter in the system.
+     */
+    public function create(array $input, Book $parentBook): Chapter
+    {
+        $chapter = new Chapter();
+        $chapter->book_id = $parentBook->id;
+        $chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1;
+        $this->baseRepo->create($chapter, $input);
+        return $chapter;
+    }
+
+    /**
+     * Update the given chapter.
+     */
+    public function update(Chapter $chapter, array $input): Chapter
+    {
+        $this->baseRepo->update($chapter, $input);
+        return $chapter;
+    }
+
+    /**
+     * Update the permissions of a chapter.
+     */
+    public function updatePermissions(Chapter $chapter, bool $restricted, Collection $permissions = null)
+    {
+        $this->baseRepo->updatePermissions($chapter, $restricted, $permissions);
+    }
+
+    /**
+     * Remove a chapter from the system.
+     * @throws Exception
+     */
+    public function destroy(Chapter $chapter)
+    {
+        $trashCan = new TrashCan();
+        $trashCan->destroyChapter($chapter);
+    }
+
+    /**
+     * Move the given chapter into a new parent book.
+     * The $parentIdentifier must be a string of the following format:
+     * 'book:<id>' (book:5)
+     * @throws MoveOperationException
+     */
+    public function move(Chapter $chapter, string $parentIdentifier): Book
+    {
+        $stringExploded = explode(':', $parentIdentifier);
+        $entityType = $stringExploded[0];
+        $entityId = intval($stringExploded[1]);
+
+        if ($entityType !== 'book') {
+            throw new MoveOperationException('Chapters can only be moved into books');
+        }
+
+        $parent = Book::visible()->where('id', '=', $entityId)->first();
+        if ($parent === null) {
+            throw new MoveOperationException('Book to move chapter into not found');
+        }
+
+        $chapter->changeBook($parent->id);
+        $chapter->rebuildPermissions();
+        return $parent;
+    }
+}
diff --git a/app/Entities/Repos/EntityRepo.php b/app/Entities/Repos/EntityRepo.php
deleted file mode 100644 (file)
index aad9a12..0000000
+++ /dev/null
@@ -1,918 +0,0 @@
-<?php namespace BookStack\Entities\Repos;
-
-use Activity;
-use BookStack\Actions\TagRepo;
-use BookStack\Actions\ViewService;
-use BookStack\Auth\Permissions\PermissionService;
-use BookStack\Auth\User;
-use BookStack\Entities\Book;
-use BookStack\Entities\Bookshelf;
-use BookStack\Entities\Chapter;
-use BookStack\Entities\Entity;
-use BookStack\Entities\EntityProvider;
-use BookStack\Entities\Page;
-use BookStack\Entities\SearchService;
-use BookStack\Exceptions\NotFoundException;
-use BookStack\Exceptions\NotifyException;
-use BookStack\Uploads\AttachmentService;
-use DOMDocument;
-use DOMNode;
-use DOMXPath;
-use Illuminate\Contracts\Pagination\LengthAwarePaginator;
-use Illuminate\Database\Eloquent\Builder;
-use Illuminate\Http\Request;
-use Illuminate\Support\Collection;
-use Throwable;
-
-class EntityRepo
-{
-
-    /**
-     * @var EntityProvider
-     */
-    protected $entityProvider;
-
-    /**
-     * @var PermissionService
-     */
-    protected $permissionService;
-
-    /**
-     * @var ViewService
-     */
-    protected $viewService;
-
-    /**
-     * @var TagRepo
-     */
-    protected $tagRepo;
-
-    /**
-     * @var SearchService
-     */
-    protected $searchService;
-
-    /**
-     * EntityRepo constructor.
-     * @param EntityProvider $entityProvider
-     * @param ViewService $viewService
-     * @param PermissionService $permissionService
-     * @param TagRepo $tagRepo
-     * @param SearchService $searchService
-     */
-    public function __construct(
-        EntityProvider $entityProvider,
-        ViewService $viewService,
-        PermissionService $permissionService,
-        TagRepo $tagRepo,
-        SearchService $searchService
-    ) {
-        $this->entityProvider = $entityProvider;
-        $this->viewService = $viewService;
-        $this->permissionService = $permissionService;
-        $this->tagRepo = $tagRepo;
-        $this->searchService = $searchService;
-    }
-
-    /**
-     * Base query for searching entities via permission system
-     * @param string $type
-     * @param bool $allowDrafts
-     * @param string $permission
-     * @return \Illuminate\Database\Query\Builder
-     */
-    protected function entityQuery($type, $allowDrafts = false, $permission = 'view')
-    {
-        $q = $this->permissionService->enforceEntityRestrictions($type, $this->entityProvider->get($type), $permission);
-        if (strtolower($type) === 'page' && !$allowDrafts) {
-            $q = $q->where('draft', '=', false);
-        }
-        return $q;
-    }
-
-    /**
-     * Check if an entity with the given id exists.
-     * @param $type
-     * @param $id
-     * @return bool
-     */
-    public function exists($type, $id)
-    {
-        return $this->entityQuery($type)->where('id', '=', $id)->exists();
-    }
-
-    /**
-     * Get an entity by ID
-     * @param string $type
-     * @param integer $id
-     * @param bool $allowDrafts
-     * @param bool $ignorePermissions
-     * @return Entity
-     */
-    public function getById($type, $id, $allowDrafts = false, $ignorePermissions = false)
-    {
-        $query = $this->entityQuery($type, $allowDrafts);
-
-        if ($ignorePermissions) {
-            $query = $this->entityProvider->get($type)->newQuery();
-        }
-
-        return $query->find($id);
-    }
-
-    /**
-     * @param string $type
-     * @param []int $ids
-     * @param bool $allowDrafts
-     * @param bool $ignorePermissions
-     * @return Builder[]|\Illuminate\Database\Eloquent\Collection|Collection
-     */
-    public function getManyById($type, $ids, $allowDrafts = false, $ignorePermissions = false)
-    {
-        $query = $this->entityQuery($type, $allowDrafts);
-
-        if ($ignorePermissions) {
-            $query = $this->entityProvider->get($type)->newQuery();
-        }
-
-        return $query->whereIn('id', $ids)->get();
-    }
-
-    /**
-     * Get an entity by its url slug.
-     * @param string $type
-     * @param string $slug
-     * @param string|bool $bookSlug
-     * @return Entity
-     * @throws NotFoundException
-     */
-    public function getBySlug($type, $slug, $bookSlug = false)
-    {
-        $q = $this->entityQuery($type)->where('slug', '=', $slug);
-
-        if (strtolower($type) === 'chapter' || strtolower($type) === 'page') {
-            $q = $q->where('book_id', '=', function ($query) use ($bookSlug) {
-                $query->select('id')
-                    ->from($this->entityProvider->book->getTable())
-                    ->where('slug', '=', $bookSlug)->limit(1);
-            });
-        }
-        $entity = $q->first();
-        if ($entity === null) {
-            throw new NotFoundException(trans('errors.' . strtolower($type) . '_not_found'));
-        }
-        return $entity;
-    }
-
-
-    /**
-     * Get all entities of a type with the given permission, limited by count unless count is false.
-     * @param string $type
-     * @param integer|bool $count
-     * @param string $permission
-     * @return Collection
-     */
-    public function getAll($type, $count = 20, $permission = 'view')
-    {
-        $q = $this->entityQuery($type, false, $permission)->orderBy('name', 'asc');
-        if ($count !== false) {
-            $q = $q->take($count);
-        }
-        return $q->get();
-    }
-
-    /**
-     * Get all entities in a paginated format
-     * @param $type
-     * @param int $count
-     * @param string $sort
-     * @param string $order
-     * @param null|callable $queryAddition
-     * @return LengthAwarePaginator
-     */
-    public function getAllPaginated($type, int $count = 10, string $sort = 'name', string $order = 'asc', $queryAddition = null)
-    {
-        $query = $this->entityQuery($type);
-        $query = $this->addSortToQuery($query, $sort, $order);
-        if ($queryAddition) {
-            $queryAddition($query);
-        }
-        return $query->paginate($count);
-    }
-
-    /**
-     * Add sorting operations to an entity query.
-     * @param Builder $query
-     * @param string $sort
-     * @param string $order
-     * @return Builder
-     */
-    protected function addSortToQuery(Builder $query, string $sort = 'name', string $order = 'asc')
-    {
-        $order = ($order === 'asc') ? 'asc' : 'desc';
-        $propertySorts = ['name', 'created_at', 'updated_at'];
-
-        if (in_array($sort, $propertySorts)) {
-            return $query->orderBy($sort, $order);
-        }
-
-        return $query;
-    }
-
-    /**
-     * Get the most recently created entities of the given type.
-     * @param string $type
-     * @param int $count
-     * @param int $page
-     * @param bool|callable $additionalQuery
-     * @return Collection
-     */
-    public function getRecentlyCreated($type, $count = 20, $page = 0, $additionalQuery = false)
-    {
-        $query = $this->permissionService->enforceEntityRestrictions($type, $this->entityProvider->get($type))
-            ->orderBy('created_at', 'desc');
-        if (strtolower($type) === 'page') {
-            $query = $query->where('draft', '=', false);
-        }
-        if ($additionalQuery !== false && is_callable($additionalQuery)) {
-            $additionalQuery($query);
-        }
-        return $query->skip($page * $count)->take($count)->get();
-    }
-
-    /**
-     * Get the most recently updated entities of the given type.
-     * @param string $type
-     * @param int $count
-     * @param int $page
-     * @param bool|callable $additionalQuery
-     * @return Collection
-     */
-    public function getRecentlyUpdated($type, $count = 20, $page = 0, $additionalQuery = false)
-    {
-        $query = $this->permissionService->enforceEntityRestrictions($type, $this->entityProvider->get($type))
-            ->orderBy('updated_at', 'desc');
-        if (strtolower($type) === 'page') {
-            $query = $query->where('draft', '=', false);
-        }
-        if ($additionalQuery !== false && is_callable($additionalQuery)) {
-            $additionalQuery($query);
-        }
-        return $query->skip($page * $count)->take($count)->get();
-    }
-
-    /**
-     * Get the most recently viewed entities.
-     * @param string|bool $type
-     * @param int $count
-     * @param int $page
-     * @return mixed
-     */
-    public function getRecentlyViewed($type, $count = 10, $page = 0)
-    {
-        $filter = is_bool($type) ? false : $this->entityProvider->get($type);
-        return $this->viewService->getUserRecentlyViewed($count, $page, $filter);
-    }
-
-    /**
-     * Get the latest pages added to the system with pagination.
-     * @param string $type
-     * @param int $count
-     * @return mixed
-     */
-    public function getRecentlyCreatedPaginated($type, $count = 20)
-    {
-        return $this->entityQuery($type)->orderBy('created_at', 'desc')->paginate($count);
-    }
-
-    /**
-     * Get the latest pages added to the system with pagination.
-     * @param string $type
-     * @param int $count
-     * @return mixed
-     */
-    public function getRecentlyUpdatedPaginated($type, $count = 20)
-    {
-        return $this->entityQuery($type)->orderBy('updated_at', 'desc')->paginate($count);
-    }
-
-    /**
-     * Get the most popular entities base on all views.
-     * @param string $type
-     * @param int $count
-     * @param int $page
-     * @return mixed
-     */
-    public function getPopular(string $type, int $count = 10, int $page = 0)
-    {
-        return $this->viewService->getPopular($count, $page, $type);
-    }
-
-    /**
-     * Get draft pages owned by the current user.
-     * @param int $count
-     * @param int $page
-     * @return Collection
-     */
-    public function getUserDraftPages($count = 20, $page = 0)
-    {
-        return $this->entityProvider->page->where('draft', '=', true)
-            ->where('created_by', '=', user()->id)
-            ->orderBy('updated_at', 'desc')
-            ->skip($count * $page)->take($count)->get();
-    }
-
-    /**
-     * Get the number of entities the given user has created.
-     * @param string $type
-     * @param User $user
-     * @return int
-     */
-    public function getUserTotalCreated(string $type, User $user)
-    {
-        return $this->entityProvider->get($type)
-            ->where('created_by', '=', $user->id)->count();
-    }
-
-    /**
-     * Get the child items for a chapter sorted by priority but
-     * with draft items floated to the top.
-     * @param Bookshelf $bookshelf
-     * @return \Illuminate\Database\Eloquent\Collection|static[]
-     */
-    public function getBookshelfChildren(Bookshelf $bookshelf)
-    {
-        return $this->permissionService->enforceEntityRestrictions('book', $bookshelf->books())->get();
-    }
-
-    /**
-     * Get the direct children of a book.
-     * @param Book $book
-     * @return \Illuminate\Database\Eloquent\Collection
-     */
-    public function getBookDirectChildren(Book $book)
-    {
-        $pages = $this->permissionService->enforceEntityRestrictions('page', $book->directPages())->get();
-        $chapters = $this->permissionService->enforceEntityRestrictions('chapters', $book->chapters())->get();
-        return collect()->concat($pages)->concat($chapters)->sortBy('priority')->sortByDesc('draft');
-    }
-
-    /**
-     * Get all child objects of a book.
-     * Returns a sorted collection of Pages and Chapters.
-     * Loads the book slug onto child elements to prevent access database access for getting the slug.
-     * @param Book $book
-     * @param bool $filterDrafts
-     * @param bool $renderPages
-     * @return mixed
-     */
-    public function getBookChildren(Book $book, $filterDrafts = false, $renderPages = false)
-    {
-        $q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts, $renderPages)->get();
-        $entities = [];
-        $parents = [];
-        $tree = [];
-
-        foreach ($q as $index => $rawEntity) {
-            if ($rawEntity->entity_type ===  $this->entityProvider->page->getMorphClass()) {
-                $entities[$index] = $this->entityProvider->page->newFromBuilder($rawEntity);
-                if ($renderPages) {
-                    $entities[$index]->html = $rawEntity->html;
-                    $entities[$index]->html = $this->renderPage($entities[$index]);
-                };
-            } else if ($rawEntity->entity_type === $this->entityProvider->chapter->getMorphClass()) {
-                $entities[$index] = $this->entityProvider->chapter->newFromBuilder($rawEntity);
-                $key = $entities[$index]->entity_type . ':' . $entities[$index]->id;
-                $parents[$key] = $entities[$index];
-                $parents[$key]->setAttribute('pages', collect());
-            }
-            if ($entities[$index]->chapter_id === 0 || $entities[$index]->chapter_id === '0') {
-                $tree[] = $entities[$index];
-            }
-            $entities[$index]->book = $book;
-        }
-
-        foreach ($entities as $entity) {
-            if ($entity->chapter_id === 0 || $entity->chapter_id === '0') {
-                continue;
-            }
-            $parentKey = $this->entityProvider->chapter->getMorphClass() . ':' . $entity->chapter_id;
-            if (!isset($parents[$parentKey])) {
-                $tree[] = $entity;
-                continue;
-            }
-            $chapter = $parents[$parentKey];
-            $chapter->pages->push($entity);
-        }
-
-        return collect($tree);
-    }
-
-    /**
-     * Get the child items for a chapter sorted by priority but
-     * with draft items floated to the top.
-     * @param Chapter $chapter
-     * @return \Illuminate\Database\Eloquent\Collection|static[]
-     */
-    public function getChapterChildren(Chapter $chapter)
-    {
-        return $this->permissionService->enforceEntityRestrictions('page', $chapter->pages())
-            ->orderBy('draft', 'DESC')->orderBy('priority', 'ASC')->get();
-    }
-
-
-    /**
-     * Get the next sequential priority for a new child element in the given book.
-     * @param Book $book
-     * @return int
-     */
-    public function getNewBookPriority(Book $book)
-    {
-        $lastElem = $this->getBookChildren($book)->pop();
-        return $lastElem ? $lastElem->priority + 1 : 0;
-    }
-
-    /**
-     * Get a new priority for a new page to be added to the given chapter.
-     * @param Chapter $chapter
-     * @return int
-     */
-    public function getNewChapterPriority(Chapter $chapter)
-    {
-        $lastPage = $chapter->pages('DESC')->first();
-        return $lastPage !== null ? $lastPage->priority + 1 : 0;
-    }
-
-    /**
-     * Find a suitable slug for an entity.
-     * @param string $type
-     * @param string $name
-     * @param bool|integer $currentId
-     * @param bool|integer $bookId Only pass if type is not a book
-     * @return string
-     */
-    public function findSuitableSlug($type, $name, $currentId = false, $bookId = false)
-    {
-        $slug = $this->nameToSlug($name);
-        while ($this->slugExists($type, $slug, $currentId, $bookId)) {
-            $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
-        }
-        return $slug;
-    }
-
-    /**
-     * Check if a slug already exists in the database.
-     * @param string $type
-     * @param string $slug
-     * @param bool|integer $currentId
-     * @param bool|integer $bookId
-     * @return bool
-     */
-    protected function slugExists($type, $slug, $currentId = false, $bookId = false)
-    {
-        $query = $this->entityProvider->get($type)->where('slug', '=', $slug);
-        if (strtolower($type) === 'page' || strtolower($type) === 'chapter') {
-            $query = $query->where('book_id', '=', $bookId);
-        }
-        if ($currentId) {
-            $query = $query->where('id', '!=', $currentId);
-        }
-        return $query->count() > 0;
-    }
-
-    /**
-     * Updates entity restrictions from a request
-     * @param Request $request
-     * @param Entity $entity
-     * @throws Throwable
-     */
-    public function updateEntityPermissionsFromRequest(Request $request, Entity $entity)
-    {
-        $entity->restricted = $request->get('restricted', '') === 'true';
-        $entity->permissions()->delete();
-
-        if ($request->filled('restrictions')) {
-            foreach ($request->get('restrictions') as $roleId => $restrictions) {
-                foreach ($restrictions as $action => $value) {
-                    $entity->permissions()->create([
-                        'role_id' => $roleId,
-                        'action'  => strtolower($action)
-                    ]);
-                }
-            }
-        }
-
-        $entity->save();
-        $this->permissionService->buildJointPermissionsForEntity($entity);
-    }
-
-
-
-    /**
-     * Create a new entity from request input.
-     * Used for books and chapters.
-     * @param string $type
-     * @param array $input
-     * @param bool|Book $book
-     * @return Entity
-     */
-    public function createFromInput($type, $input = [], $book = false)
-    {
-        $isChapter = strtolower($type) === 'chapter';
-        $entityModel = $this->entityProvider->get($type)->newInstance($input);
-        $entityModel->slug = $this->findSuitableSlug($type, $entityModel->name, false, $isChapter ? $book->id : false);
-        $entityModel->created_by = user()->id;
-        $entityModel->updated_by = user()->id;
-        $isChapter ? $book->chapters()->save($entityModel) : $entityModel->save();
-
-        if (isset($input['tags'])) {
-            $this->tagRepo->saveTagsToEntity($entityModel, $input['tags']);
-        }
-
-        $this->permissionService->buildJointPermissionsForEntity($entityModel);
-        $this->searchService->indexEntity($entityModel);
-        return $entityModel;
-    }
-
-    /**
-     * Update entity details from request input.
-     * Used for books and chapters
-     * @param string $type
-     * @param Entity $entityModel
-     * @param array $input
-     * @return Entity
-     */
-    public function updateFromInput($type, Entity $entityModel, $input = [])
-    {
-        if ($entityModel->name !== $input['name']) {
-            $entityModel->slug = $this->findSuitableSlug($type, $input['name'], $entityModel->id);
-        }
-        $entityModel->fill($input);
-        $entityModel->updated_by = user()->id;
-        $entityModel->save();
-
-        if (isset($input['tags'])) {
-            $this->tagRepo->saveTagsToEntity($entityModel, $input['tags']);
-        }
-
-        $this->permissionService->buildJointPermissionsForEntity($entityModel);
-        $this->searchService->indexEntity($entityModel);
-        return $entityModel;
-    }
-
-    /**
-     * Sync the books assigned to a shelf from a comma-separated list
-     * of book IDs.
-     * @param Bookshelf $shelf
-     * @param string $books
-     */
-    public function updateShelfBooks(Bookshelf $shelf, string $books)
-    {
-        $ids = explode(',', $books);
-
-        // Check books exist and match ordering
-        $bookIds = $this->entityQuery('book')->whereIn('id', $ids)->get(['id'])->pluck('id');
-        $syncData = [];
-        foreach ($ids as $index => $id) {
-            if ($bookIds->contains($id)) {
-                $syncData[$id] = ['order' => $index];
-            }
-        }
-
-        $shelf->books()->sync($syncData);
-    }
-
-    /**
-     * Append a Book to a BookShelf.
-     * @param Bookshelf $shelf
-     * @param Book $book
-     */
-    public function appendBookToShelf(Bookshelf $shelf, Book $book)
-    {
-        if ($shelf->contains($book)) {
-            return;
-        }
-
-        $maxOrder = $shelf->books()->max('order');
-        $shelf->books()->attach($book->id, ['order' => $maxOrder + 1]);
-    }
-
-    /**
-     * Change the book that an entity belongs to.
-     * @param string $type
-     * @param integer $newBookId
-     * @param Entity $entity
-     * @param bool $rebuildPermissions
-     * @return Entity
-     */
-    public function changeBook($type, $newBookId, Entity $entity, $rebuildPermissions = false)
-    {
-        $entity->book_id = $newBookId;
-        // Update related activity
-        foreach ($entity->activity as $activity) {
-            $activity->book_id = $newBookId;
-            $activity->save();
-        }
-        $entity->slug = $this->findSuitableSlug($type, $entity->name, $entity->id, $newBookId);
-        $entity->save();
-
-        // Update all child pages if a chapter
-        if (strtolower($type) === 'chapter') {
-            foreach ($entity->pages as $page) {
-                $this->changeBook('page', $newBookId, $page, false);
-            }
-        }
-
-        // Update permissions if applicable
-        if ($rebuildPermissions) {
-            $entity->load('book');
-            $this->permissionService->buildJointPermissionsForEntity($entity->book);
-        }
-
-        return $entity;
-    }
-
-    /**
-     * Alias method to update the book jointPermissions in the PermissionService.
-     * @param Book $book
-     */
-    public function buildJointPermissionsForBook(Book $book)
-    {
-        $this->permissionService->buildJointPermissionsForEntity($book);
-    }
-
-    /**
-     * Format a name as a url slug.
-     * @param $name
-     * @return string
-     */
-    protected function nameToSlug($name)
-    {
-        $slug = preg_replace('/[\+\/\\\?\@\}\{\.\,\=\[\]\#\&\!\*\'\;\:\$\%]/', '', mb_strtolower($name));
-        $slug = preg_replace('/\s{2,}/', ' ', $slug);
-        $slug = str_replace(' ', '-', $slug);
-        if ($slug === "") {
-            $slug = substr(md5(rand(1, 500)), 0, 5);
-        }
-        return $slug;
-    }
-
-    /**
-     * Render the page for viewing
-     * @param Page $page
-     * @param bool $blankIncludes
-     * @return string
-     */
-    public function renderPage(Page $page, bool $blankIncludes = false) : string
-    {
-        $content = $page->html;
-
-        if (!config('app.allow_content_scripts')) {
-            $content = $this->escapeScripts($content);
-        }
-
-        if ($blankIncludes) {
-            $content = $this->blankPageIncludes($content);
-        } else {
-            $content = $this->parsePageIncludes($content);
-        }
-
-        return $content;
-    }
-
-    /**
-     * Remove any page include tags within the given HTML.
-     * @param string $html
-     * @return string
-     */
-    protected function blankPageIncludes(string $html) : string
-    {
-        return preg_replace("/{{@\s?([0-9].*?)}}/", '', $html);
-    }
-
-    /**
-     * Parse any include tags "{{@<page_id>#section}}" to be part of the page.
-     * @param string $html
-     * @return mixed|string
-     */
-    protected function parsePageIncludes(string $html) : string
-    {
-        $matches = [];
-        preg_match_all("/{{@\s?([0-9].*?)}}/", $html, $matches);
-
-        $topLevelTags = ['table', 'ul', 'ol'];
-        foreach ($matches[1] as $index => $includeId) {
-            $splitInclude = explode('#', $includeId, 2);
-            $pageId = intval($splitInclude[0]);
-            if (is_nan($pageId)) {
-                continue;
-            }
-
-            $matchedPage = $this->getById('page', $pageId);
-            if ($matchedPage === null) {
-                $html = str_replace($matches[0][$index], '', $html);
-                continue;
-            }
-
-            if (count($splitInclude) === 1) {
-                $html = str_replace($matches[0][$index], $matchedPage->html, $html);
-                continue;
-            }
-
-            $doc = new DOMDocument();
-            libxml_use_internal_errors(true);
-            $doc->loadHTML(mb_convert_encoding('<body>'.$matchedPage->html.'</body>', 'HTML-ENTITIES', 'UTF-8'));
-            $matchingElem = $doc->getElementById($splitInclude[1]);
-            if ($matchingElem === null) {
-                $html = str_replace($matches[0][$index], '', $html);
-                continue;
-            }
-            $innerContent = '';
-            $isTopLevel = in_array(strtolower($matchingElem->nodeName), $topLevelTags);
-            if ($isTopLevel) {
-                $innerContent .= $doc->saveHTML($matchingElem);
-            } else {
-                foreach ($matchingElem->childNodes as $childNode) {
-                    $innerContent .= $doc->saveHTML($childNode);
-                }
-            }
-            libxml_clear_errors();
-            $html = str_replace($matches[0][$index], trim($innerContent), $html);
-        }
-
-        return $html;
-    }
-
-    /**
-     * Escape script tags within HTML content.
-     * @param string $html
-     * @return string
-     */
-    protected function escapeScripts(string $html) : string
-    {
-        if ($html == '') {
-            return $html;
-        }
-
-        libxml_use_internal_errors(true);
-        $doc = new DOMDocument();
-        $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
-        $xPath = new DOMXPath($doc);
-
-        // Remove standard script tags
-        $scriptElems = $xPath->query('//script');
-        foreach ($scriptElems as $scriptElem) {
-            $scriptElem->parentNode->removeChild($scriptElem);
-        }
-
-        // Remove 'on*' attributes
-        $onAttributes = $xPath->query('//@*[starts-with(name(), \'on\')]');
-        foreach ($onAttributes as $attr) {
-            /** @var \DOMAttr $attr*/
-            $attrName = $attr->nodeName;
-            $attr->parentNode->removeAttribute($attrName);
-        }
-
-        $html = '';
-        $topElems = $doc->documentElement->childNodes->item(0)->childNodes;
-        foreach ($topElems as $child) {
-            $html .= $doc->saveHTML($child);
-        }
-
-        return $html;
-    }
-
-    /**
-     * Search for image usage within page content.
-     * @param $imageString
-     * @return mixed
-     */
-    public function searchForImage($imageString)
-    {
-        $pages = $this->entityQuery('page')->where('html', 'like', '%' . $imageString . '%')->get(['id', 'name', 'slug', 'book_id']);
-        foreach ($pages as $page) {
-            $page->url = $page->getUrl();
-            $page->html = '';
-            $page->text = '';
-        }
-        return count($pages) > 0 ? $pages : false;
-    }
-
-    /**
-     * Destroy a bookshelf instance
-     * @param Bookshelf $shelf
-     * @throws Throwable
-     */
-    public function destroyBookshelf(Bookshelf $shelf)
-    {
-        $this->destroyEntityCommonRelations($shelf);
-        $shelf->delete();
-    }
-
-    /**
-     * Destroy the provided book and all its child entities.
-     * @param Book $book
-     * @throws NotifyException
-     * @throws Throwable
-     */
-    public function destroyBook(Book $book)
-    {
-        foreach ($book->pages as $page) {
-            $this->destroyPage($page);
-        }
-        foreach ($book->chapters as $chapter) {
-            $this->destroyChapter($chapter);
-        }
-        $this->destroyEntityCommonRelations($book);
-        $book->delete();
-    }
-
-    /**
-     * Destroy a chapter and its relations.
-     * @param Chapter $chapter
-     * @throws Throwable
-     */
-    public function destroyChapter(Chapter $chapter)
-    {
-        if (count($chapter->pages) > 0) {
-            foreach ($chapter->pages as $page) {
-                $page->chapter_id = 0;
-                $page->save();
-            }
-        }
-        $this->destroyEntityCommonRelations($chapter);
-        $chapter->delete();
-    }
-
-    /**
-     * Destroy a given page along with its dependencies.
-     * @param Page $page
-     * @throws NotifyException
-     * @throws Throwable
-     */
-    public function destroyPage(Page $page)
-    {
-        // Check if set as custom homepage & remove setting if not used or throw error if active
-        $customHome = setting('app-homepage', '0:');
-        if (intval($page->id) === intval(explode(':', $customHome)[0])) {
-            if (setting('app-homepage-type') === 'page') {
-                throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl());
-            }
-            setting()->remove('app-homepage');
-        }
-
-        $this->destroyEntityCommonRelations($page);
-
-        // Delete Attached Files
-        $attachmentService = app(AttachmentService::class);
-        foreach ($page->attachments as $attachment) {
-            $attachmentService->deleteFile($attachment);
-        }
-
-        $page->delete();
-    }
-
-    /**
-     * Destroy or handle the common relations connected to an entity.
-     * @param Entity $entity
-     * @throws Throwable
-     */
-    protected function destroyEntityCommonRelations(Entity $entity)
-    {
-        Activity::removeEntity($entity);
-        $entity->views()->delete();
-        $entity->permissions()->delete();
-        $entity->tags()->delete();
-        $entity->comments()->delete();
-        $this->permissionService->deleteJointPermissionsForEntity($entity);
-        $this->searchService->deleteEntityTerms($entity);
-    }
-
-    /**
-     * Copy the permissions of a bookshelf to all child books.
-     * Returns the number of books that had permissions updated.
-     * @param Bookshelf $bookshelf
-     * @return int
-     * @throws Throwable
-     */
-    public function copyBookshelfPermissions(Bookshelf $bookshelf)
-    {
-        $shelfPermissions = $bookshelf->permissions()->get(['role_id', 'action'])->toArray();
-        $shelfBooks = $bookshelf->books()->get();
-        $updatedBookCount = 0;
-
-        foreach ($shelfBooks as $book) {
-            if (!userCan('restrictions-manage', $book)) {
-                continue;
-            }
-            $book->permissions()->delete();
-            $book->restricted = $bookshelf->restricted;
-            $book->permissions()->createMany($shelfPermissions);
-            $book->save();
-            $this->permissionService->buildJointPermissionsForEntity($book);
-            $updatedBookCount++;
-        }
-
-        return $updatedBookCount;
-    }
-}
index 6b004984f389e5f7d553ccbf366bf23ef148e292..e49eeb1ef5518eb8aa124d11e981236f11ebbe04 100644 (file)
 use BookStack\Entities\Book;
 use BookStack\Entities\Chapter;
 use BookStack\Entities\Entity;
+use BookStack\Entities\Managers\BookContents;
+use BookStack\Entities\Managers\PageContent;
+use BookStack\Entities\Managers\TrashCan;
 use BookStack\Entities\Page;
 use BookStack\Entities\PageRevision;
-use Carbon\Carbon;
-use DOMDocument;
-use DOMElement;
-use DOMXPath;
-
-class PageRepo extends EntityRepo
+use BookStack\Exceptions\MoveOperationException;
+use BookStack\Exceptions\NotFoundException;
+use BookStack\Exceptions\NotifyException;
+use BookStack\Exceptions\PermissionsException;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Pagination\LengthAwarePaginator;
+use Illuminate\Support\Collection;
+
+class PageRepo
 {
 
+    protected $baseRepo;
+
     /**
-     * Get page by slug.
-     * @param string $pageSlug
-     * @param string $bookSlug
-     * @return Page
-     * @throws \BookStack\Exceptions\NotFoundException
+     * PageRepo constructor.
      */
-    public function getPageBySlug(string $pageSlug, string $bookSlug)
+    public function __construct(BaseRepo $baseRepo)
     {
-        return $this->getBySlug('page', $pageSlug, $bookSlug);
+        $this->baseRepo = $baseRepo;
     }
 
     /**
-     * Search through page revisions and retrieve the last page in the
-     * current book that has a slug equal to the one given.
-     * @param string $pageSlug
-     * @param string $bookSlug
-     * @return null|Page
+     * Get a page by ID.
+     * @throws NotFoundException
      */
-    public function getPageByOldSlug(string $pageSlug, string $bookSlug)
+    public function getById(int $id): Page
     {
-        $revision = $this->entityProvider->pageRevision->where('slug', '=', $pageSlug)
-            ->whereHas('page', function ($query) {
-                $this->permissionService->enforceEntityRestrictions('page', $query);
-            })
-            ->where('type', '=', 'version')
-            ->where('book_slug', '=', $bookSlug)
-            ->orderBy('created_at', 'desc')
-            ->with('page')->first();
-        return $revision !== null ? $revision->page : null;
+        $page = Page::visible()->with(['book'])->find($id);
+
+        if (!$page) {
+            throw new NotFoundException(trans('errors.page_not_found'));
+        }
+
+        return $page;
     }
 
     /**
-     * Updates a page with any fillable data and saves it into the database.
-     * @param Page $page
-     * @param int $book_id
-     * @param array $input
-     * @return Page
-     * @throws \Exception
+     * Get a page its book and own slug.
+     * @throws NotFoundException
      */
-    public function updatePage(Page $page, int $book_id, array $input)
+    public function getBySlug(string $bookSlug, string $pageSlug): Page
     {
-        // Hold the old details to compare later
-        $oldHtml = $page->html;
-        $oldName = $page->name;
+        $page = Page::visible()->whereSlugs($bookSlug, $pageSlug)->first();
 
-        // Prevent slug being updated if no name change
-        if ($page->name !== $input['name']) {
-            $page->slug = $this->findSuitableSlug('page', $input['name'], $page->id, $book_id);
+        if (!$page) {
+            throw new NotFoundException(trans('errors.page_not_found'));
         }
 
-        // Save page tags if present
-        if (isset($input['tags'])) {
-            $this->tagRepo->saveTagsToEntity($page, $input['tags']);
-        }
-
-        // Update with new details
-        $userId = user()->id;
-        $page->fill($input);
-        $page->html = $this->formatHtml($input['html']);
-        $page->text = $this->pageToPlainText($page);
-        if (setting('app-editor') !== 'markdown') {
-            $page->markdown = '';
-        }
-        $page->updated_by = $userId;
-        $page->revision_count++;
-        $page->save();
+        return $page;
+    }
 
-        // Remove all update drafts for this user & page.
-        $this->userUpdatePageDraftsQuery($page, $userId)->delete();
+    /**
+     * Get a page by its old slug but checking the revisions table
+     * for the last revision that matched the given page and book slug.
+     */
+    public function getByOldSlug(string $bookSlug, string $pageSlug): ?Page
+    {
+        $revision = PageRevision::query()
+            ->whereHas('page', function (Builder $query) {
+                $query->visible();
+            })
+            ->where('slug', '=', $pageSlug)
+            ->where('type', '=', 'version')
+            ->where('book_slug', '=', $bookSlug)
+            ->orderBy('created_at', 'desc')
+            ->with('page')
+            ->first();
+        return $revision ? $revision->page : null;
+    }
 
-        // Save a revision after updating
-        if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) {
-            $this->savePageRevision($page, $input['summary']);
+    /**
+     * Get pages that have been marked as a template.
+     */
+    public function getTemplates(int $count = 10, int $page = 1, string $search = ''): LengthAwarePaginator
+    {
+        $query = Page::visible()
+            ->where('template', '=', true)
+            ->orderBy('name', 'asc')
+            ->skip(($page - 1) * $count)
+            ->take($count);
+
+        if ($search) {
+            $query->where('name', 'like', '%' . $search . '%');
         }
 
-        $this->searchService->indexEntity($page);
+        $paginator = $query->paginate($count, ['*'], 'page', $page);
+        $paginator->withPath('/templates');
 
-        return $page;
+        return $paginator;
     }
 
     /**
-     * Saves a page revision into the system.
-     * @param Page $page
-     * @param null|string $summary
-     * @return PageRevision
-     * @throws \Exception
+     * Get a parent item via slugs.
      */
-    public function savePageRevision(Page $page, string $summary = null)
+    public function getParentFromSlugs(string $bookSlug, string $chapterSlug = null): Entity
     {
-        $revision = $this->entityProvider->pageRevision->newInstance($page->toArray());
-        if (setting('app-editor') !== 'markdown') {
-            $revision->markdown = '';
+        if ($chapterSlug !== null) {
+            return $chapter = Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail();
         }
-        $revision->page_id = $page->id;
-        $revision->slug = $page->slug;
-        $revision->book_slug = $page->book->slug;
-        $revision->created_by = user()->id;
-        $revision->created_at = $page->updated_at;
-        $revision->type = 'version';
-        $revision->summary = $summary;
-        $revision->revision_number = $page->revision_count;
-        $revision->save();
 
-        $revisionLimit = config('app.revision_limit');
-        if ($revisionLimit !== false) {
-            $revisionsToDelete = $this->entityProvider->pageRevision->where('page_id', '=', $page->id)
-                ->orderBy('created_at', 'desc')->skip(intval($revisionLimit))->take(10)->get(['id']);
-            if ($revisionsToDelete->count() > 0) {
-                $this->entityProvider->pageRevision->whereIn('id', $revisionsToDelete->pluck('id'))->delete();
-            }
-        }
+        return Book::visible()->where('slug', '=', $bookSlug)->firstOrFail();
+    }
 
+    /**
+     * Get the draft copy of the given page for the current user.
+     */
+    public function getUserDraft(Page $page): ?PageRevision
+    {
+        $revision = $this->getUserDraftQuery($page)->first();
         return $revision;
     }
 
     /**
-     * Formats a page's html to be tagged correctly within the system.
-     * @param string $htmlText
-     * @return string
+     * Get a new draft page belonging to the given parent entity.
      */
-    protected function formatHtml(string $htmlText)
+    public function getNewDraftPage(Entity $parent)
     {
-        if ($htmlText == '') {
-            return $htmlText;
+        $page = (new Page())->forceFill([
+            'name' => trans('entities.pages_initial_name'),
+            'created_by' => user()->id,
+            'updated_by' => user()->id,
+            'draft' => true,
+        ]);
+
+        if ($parent instanceof Chapter) {
+            $page->chapter_id = $parent->id;
+            $page->book_id = $parent->book_id;
+        } else {
+            $page->book_id = $parent->id;
         }
 
-        libxml_use_internal_errors(true);
-        $doc = new DOMDocument();
-        $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
-
-        $container = $doc->documentElement;
-        $body = $container->childNodes->item(0);
-        $childNodes = $body->childNodes;
-
-        // Set ids on top-level nodes
-        $idMap = [];
-        foreach ($childNodes as $index => $childNode) {
-            $this->setUniqueId($childNode, $idMap);
-        }
+        $page->save();
+        $page->refresh()->rebuildPermissions();
+        return $page;
+    }
 
-        // Ensure no duplicate ids within child items
-        $xPath = new DOMXPath($doc);
-        $idElems = $xPath->query('//body//*//*[@id]');
-        foreach ($idElems as $domElem) {
-            $this->setUniqueId($domElem, $idMap);
+    /**
+     * Publish a draft page to make it a live, non-draft page.
+     */
+    public function publishDraft(Page $draft, array $input): Page
+    {
+        $this->baseRepo->update($draft, $input);
+        if (isset($input['template']) && userCan('templates-manage')) {
+            $draft->template = ($input['template'] === 'true');
         }
 
-        // Generate inner html as a string
-        $html = '';
-        foreach ($childNodes as $childNode) {
-            $html .= $doc->saveHTML($childNode);
-        }
+        $pageContent = new PageContent($draft);
+        $pageContent->setNewHTML($input['html']);
+        $draft->draft = false;
+        $draft->revision_count = 1;
+        $draft->priority = $this->getNewPriority($draft);
+        $draft->refreshSlug();
+        $draft->save();
 
-        return $html;
+        $this->savePageRevision($draft, trans('entities.pages_initial_revision'));
+        $draft->indexForSearch();
+        return $draft->refresh();
     }
 
     /**
-     * Set a unique id on the given DOMElement.
-     * A map for existing ID's should be passed in to check for current existence.
-     * @param DOMElement $element
-     * @param array $idMap
+     * Update a page in the system.
      */
-    protected function setUniqueId($element, array &$idMap)
+    public function update(Page $page, array $input): Page
     {
-        if (get_class($element) !== 'DOMElement') {
-            return;
-        }
+        // Hold the old details to compare later
+        $oldHtml = $page->html;
+        $oldName = $page->name;
 
-        // Overwrite id if not a BookStack custom id
-        $existingId = $element->getAttribute('id');
-        if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) {
-            $idMap[$existingId] = true;
-            return;
+        if (isset($input['template']) && userCan('templates-manage')) {
+            $page->template = ($input['template'] === 'true');
         }
 
-        // Create an unique id for the element
-        // Uses the content as a basis to ensure output is the same every time
-        // the same content is passed through.
-        $contentId = 'bkmrk-' . mb_substr(strtolower(preg_replace('/\s+/', '-', trim($element->nodeValue))), 0, 20);
-        $newId = urlencode($contentId);
-        $loopIndex = 0;
+        $this->baseRepo->update($page, $input);
 
-        while (isset($idMap[$newId])) {
-            $newId = urlencode($contentId . '-' . $loopIndex);
-            $loopIndex++;
+        // Update with new details
+        $page->fill($input);
+        $pageContent = new PageContent($page);
+        $pageContent->setNewHTML($input['html']);
+        $page->revision_count++;
+
+        if (setting('app-editor') !== 'markdown') {
+            $page->markdown = '';
         }
 
-        $element->setAttribute('id', $newId);
-        $idMap[$newId] = true;
-    }
+        $page->save();
 
-    /**
-     * Get the plain text version of a page's content.
-     * @param \BookStack\Entities\Page $page
-     * @return string
-     */
-    protected function pageToPlainText(Page $page) : string
-    {
-        $html = $this->renderPage($page, true);
-        return strip_tags($html);
+        // Remove all update drafts for this user & page.
+        $this->getUserDraftQuery($page)->delete();
+
+        // Save a revision after updating
+        $summary = $input['summary'] ?? null;
+        if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $summary !== null) {
+            $this->savePageRevision($page, $summary);
+        }
+
+        return $page;
     }
 
     /**
-     * Get a new draft page instance.
-     * @param Book $book
-     * @param Chapter|null $chapter
-     * @return \BookStack\Entities\Page
-     * @throws \Throwable
+     * Saves a page revision into the system.
      */
-    public function getDraftPage(Book $book, Chapter $chapter = null)
+    protected function savePageRevision(Page $page, string $summary = null)
     {
-        $page = $this->entityProvider->page->newInstance();
-        $page->name = trans('entities.pages_initial_name');
-        $page->created_by = user()->id;
-        $page->updated_by = user()->id;
-        $page->draft = true;
+        $revision = new PageRevision($page->toArray());
 
-        if ($chapter) {
-            $page->chapter_id = $chapter->id;
+        if (setting('app-editor') !== 'markdown') {
+            $revision->markdown = '';
         }
 
-        $book->pages()->save($page);
-        $page = $this->entityProvider->page->find($page->id);
-        $this->permissionService->buildJointPermissionsForEntity($page);
-        return $page;
+        $revision->page_id = $page->id;
+        $revision->slug = $page->slug;
+        $revision->book_slug = $page->book->slug;
+        $revision->created_by = user()->id;
+        $revision->created_at = $page->updated_at;
+        $revision->type = 'version';
+        $revision->summary = $summary;
+        $revision->revision_number = $page->revision_count;
+        $revision->save();
+
+        $this->deleteOldRevisions($page);
+        return $revision;
     }
 
     /**
      * Save a page update draft.
-     * @param Page $page
-     * @param array $data
-     * @return PageRevision|Page
      */
-    public function updatePageDraft(Page $page, array $data = [])
+    public function updatePageDraft(Page $page, array $input)
     {
         // If the page itself is a draft simply update that
         if ($page->draft) {
-            $page->fill($data);
-            if (isset($data['html'])) {
-                $page->text = $this->pageToPlainText($page);
+            $page->fill($input);
+            if (isset($input['html'])) {
+                $content = new PageContent($page);
+                $content->setNewHTML($input['html']);
             }
             $page->save();
             return $page;
         }
 
         // Otherwise save the data to a revision
-        $userId = user()->id;
-        $drafts = $this->userUpdatePageDraftsQuery($page, $userId)->get();
-
-        if ($drafts->count() > 0) {
-            $draft = $drafts->first();
-        } else {
-            $draft = $this->entityProvider->pageRevision->newInstance();
-            $draft->page_id = $page->id;
-            $draft->slug = $page->slug;
-            $draft->book_slug = $page->book->slug;
-            $draft->created_by = $userId;
-            $draft->type = 'update_draft';
-        }
-
-        $draft->fill($data);
+        $draft = $this->getPageRevisionToUpdate($page);
+        $draft->fill($input);
         if (setting('app-editor') !== 'markdown') {
             $draft->markdown = '';
         }
@@ -284,243 +259,203 @@ class PageRepo extends EntityRepo
     }
 
     /**
-     * Publish a draft page to make it a normal page.
-     * Sets the slug and updates the content.
-     * @param Page $draftPage
-     * @param array $input
-     * @return Page
-     * @throws \Exception
+     * Destroy a page from the system.
+     * @throws NotifyException
      */
-    public function publishPageDraft(Page $draftPage, array $input)
+    public function destroy(Page $page)
     {
-        $draftPage->fill($input);
-
-        // Save page tags if present
-        if (isset($input['tags'])) {
-            $this->tagRepo->saveTagsToEntity($draftPage, $input['tags']);
-        }
-
-        $draftPage->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id);
-        $draftPage->html = $this->formatHtml($input['html']);
-        $draftPage->text = $this->pageToPlainText($draftPage);
-        $draftPage->draft = false;
-        $draftPage->revision_count = 1;
-
-        $draftPage->save();
-        $this->savePageRevision($draftPage, trans('entities.pages_initial_revision'));
-        $this->searchService->indexEntity($draftPage);
-        return $draftPage;
+        $trashCan = new TrashCan();
+        $trashCan->destroyPage($page);
     }
 
     /**
-     * The base query for getting user update drafts.
-     * @param Page $page
-     * @param $userId
-     * @return mixed
+     * Restores a revision's content back into a page.
      */
-    protected function userUpdatePageDraftsQuery(Page $page, int $userId)
+    public function restoreRevision(Page $page, int $revisionId): Page
     {
-        return $this->entityProvider->pageRevision->where('created_by', '=', $userId)
-            ->where('type', 'update_draft')
-            ->where('page_id', '=', $page->id)
-            ->orderBy('created_at', 'desc');
+        $page->revision_count++;
+        $this->savePageRevision($page);
+
+        $revision = $page->revisions()->where('id', '=', $revisionId)->first();
+        $page->fill($revision->toArray());
+        $content = new PageContent($page);
+        $content->setNewHTML($page->html);
+        $page->updated_by = user()->id;
+        $page->refreshSlug();
+        $page->save();
+
+        $page->indexForSearch();
+        return $page;
     }
 
     /**
-     * Get the latest updated draft revision for a particular page and user.
-     * @param Page $page
-     * @param $userId
-     * @return PageRevision|null
+     * Move the given page into a new parent book or chapter.
+     * The $parentIdentifier must be a string of the following format:
+     * 'book:<id>' (book:5)
+     * @throws MoveOperationException
+     * @throws PermissionsException
      */
-    public function getUserPageDraft(Page $page, int $userId)
+    public function move(Page $page, string $parentIdentifier): Book
     {
-        return $this->userUpdatePageDraftsQuery($page, $userId)->first();
+        $parent = $this->findParentByIdentifier($parentIdentifier);
+        if ($parent === null) {
+            throw new MoveOperationException('Book or chapter to move page into not found');
+        }
+
+        if (!userCan('page-create', $parent)) {
+            throw new PermissionsException('User does not have permission to create a page within the new parent');
+        }
+
+        $page->chapter_id = ($parent instanceof Chapter) ? $parent->id : null;
+        $page->changeBook($parent instanceof Book ? $parent->id : $parent->book->id);
+        $page->rebuildPermissions();
+
+        return ($parent instanceof Book ? $parent : $parent->book);
     }
 
     /**
-     * Get the notification message that informs the user that they are editing a draft page.
-     * @param PageRevision $draft
-     * @return string
+     * Copy an existing page in the system.
+     * Optionally providing a new parent via string identifier and a new name.
+     * @throws MoveOperationException
+     * @throws PermissionsException
      */
-    public function getUserPageDraftMessage(PageRevision $draft)
+    public function copy(Page $page, string $parentIdentifier = null, string $newName = null): Page
     {
-        $message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]);
-        if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) {
-            return $message;
+        $parent = $parentIdentifier ? $this->findParentByIdentifier($parentIdentifier) : $page->parent();
+        if ($parent === null) {
+            throw new MoveOperationException('Book or chapter to move page into not found');
         }
-        return $message . "\n" . trans('entities.pages_draft_edited_notification');
+
+        if (!userCan('page-create', $parent)) {
+            throw new PermissionsException('User does not have permission to create a page within the new parent');
+        }
+
+        $copyPage = $this->getNewDraftPage($parent);
+        $pageData = $page->getAttributes();
+
+        // Update name
+        if (!empty($newName)) {
+            $pageData['name'] = $newName;
+        }
+
+        // Copy tags from previous page if set
+        if ($page->tags) {
+            $pageData['tags'] = [];
+            foreach ($page->tags as $tag) {
+                $pageData['tags'][] = ['name' => $tag->name, 'value' => $tag->value];
+            }
+        }
+
+        return $this->publishDraft($copyPage, $pageData);
     }
 
     /**
-     * A query to check for active update drafts on a particular page.
-     * @param Page $page
-     * @param int $minRange
-     * @return mixed
+     * Find a page parent entity via a identifier string in the format:
+     * {type}:{id}
+     * Example: (book:5)
+     * @throws MoveOperationException
      */
-    protected function activePageEditingQuery(Page $page, int $minRange = null)
+    protected function findParentByIdentifier(string $identifier): ?Entity
     {
-        $query = $this->entityProvider->pageRevision->where('type', '=', 'update_draft')
-            ->where('page_id', '=', $page->id)
-            ->where('updated_at', '>', $page->updated_at)
-            ->where('created_by', '!=', user()->id)
-            ->with('createdBy');
+        $stringExploded = explode(':', $identifier);
+        $entityType = $stringExploded[0];
+        $entityId = intval($stringExploded[1]);
 
-        if ($minRange !== null) {
-            $query = $query->where('updated_at', '>=', Carbon::now()->subMinutes($minRange));
+        if ($entityType !== 'book' && $entityType !== 'chapter') {
+            throw new MoveOperationException('Pages can only be in books or chapters');
         }
 
-        return $query;
+        $parentClass = $entityType === 'book' ? Book::class : Chapter::class;
+        return $parentClass::visible()->where('id', '=', $entityId)->first();
     }
 
     /**
-     * Check if a page is being actively editing.
-     * Checks for edits since last page updated.
-     * Passing in a minuted range will check for edits
-     * within the last x minutes.
-     * @param Page $page
-     * @param int $minRange
-     * @return bool
+     * Update the permissions of a page.
      */
-    public function isPageEditingActive(Page $page, int $minRange = null)
+    public function updatePermissions(Page $page, bool $restricted, Collection $permissions = null)
     {
-        $draftSearch = $this->activePageEditingQuery($page, $minRange);
-        return $draftSearch->count() > 0;
+        $this->baseRepo->updatePermissions($page, $restricted, $permissions);
     }
 
     /**
-     * Get a notification message concerning the editing activity on a particular page.
-     * @param Page $page
-     * @param int $minRange
-     * @return string
+     * Change the page's parent to the given entity.
      */
-    public function getPageEditingActiveMessage(Page $page, int $minRange = null)
+    protected function changeParent(Page $page, Entity $parent)
     {
-        $pageDraftEdits = $this->activePageEditingQuery($page, $minRange)->get();
+        $book = ($parent instanceof Book) ? $parent : $parent->book;
+        $page->chapter_id = ($parent instanceof Chapter) ? $parent->id : 0;
+        $page->save();
 
-        $userMessage = $pageDraftEdits->count() > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $pageDraftEdits->count()]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
-        $timeMessage = $minRange === null ? trans('entities.pages_draft_edit_active.time_a') : trans('entities.pages_draft_edit_active.time_b', ['minCount'=>$minRange]);
-        return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]);
+        if ($page->book->id !== $book->id) {
+            $page->changeBook($book->id);
+        }
+
+        $page->load('book');
+        $book->rebuildPermissions();
     }
 
     /**
-     * Parse the headers on the page to get a navigation menu
-     * @param string $pageContent
-     * @return array
+     * Get a page revision to update for the given page.
+     * Checks for an existing revisions before providing a fresh one.
      */
-    public function getPageNav(string $pageContent)
+    protected function getPageRevisionToUpdate(Page $page): PageRevision
     {
-        if ($pageContent == '') {
-            return [];
-        }
-        libxml_use_internal_errors(true);
-        $doc = new DOMDocument();
-        $doc->loadHTML(mb_convert_encoding($pageContent, 'HTML-ENTITIES', 'UTF-8'));
-        $xPath = new DOMXPath($doc);
-        $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
-
-        if (is_null($headers)) {
-            return [];
+        $drafts = $this->getUserDraftQuery($page)->get();
+        if ($drafts->count() > 0) {
+            return $drafts->first();
         }
 
-        $tree = collect($headers)->map(function($header) {
-            $text = trim(str_replace("\xc2\xa0", '', $header->nodeValue));
-            $text = mb_substr($text, 0, 100);
-
-            return [
-                'nodeName' => strtolower($header->nodeName),
-                'level' => intval(str_replace('h', '', $header->nodeName)),
-                'link' => '#' . $header->getAttribute('id'),
-                'text' => $text,
-            ];
-        })->filter(function($header) {
-            return mb_strlen($header['text']) > 0;
-        });
-
-        // Shift headers if only smaller headers have been used
-        $levelChange = ($tree->pluck('level')->min() - 1);
-        $tree = $tree->map(function ($header) use ($levelChange) {
-            $header['level'] -= ($levelChange);
-            return $header;
-        });
-
-        return $tree->toArray();
+        $draft = new PageRevision();
+        $draft->page_id = $page->id;
+        $draft->slug = $page->slug;
+        $draft->book_slug = $page->book->slug;
+        $draft->created_by = user()->id;
+        $draft->type = 'update_draft';
+        return $draft;
     }
 
     /**
-     * Restores a revision's content back into a page.
-     * @param Page $page
-     * @param Book $book
-     * @param  int $revisionId
-     * @return Page
-     * @throws \Exception
+     * Delete old revisions, for the given page, from the system.
      */
-    public function restorePageRevision(Page $page, Book $book, int $revisionId)
+    protected function deleteOldRevisions(Page $page)
     {
-        $page->revision_count++;
-        $this->savePageRevision($page);
-        $revision = $page->revisions()->where('id', '=', $revisionId)->first();
-        $page->fill($revision->toArray());
-        $page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id);
-        $page->text = $this->pageToPlainText($page);
-        $page->updated_by = user()->id;
-        $page->save();
-        $this->searchService->indexEntity($page);
-        return $page;
+        $revisionLimit = config('app.revision_limit');
+        if ($revisionLimit === false) {
+            return;
+        }
+
+        $revisionsToDelete = PageRevision::query()
+            ->where('page_id', '=', $page->id)
+            ->orderBy('created_at', 'desc')
+            ->skip(intval($revisionLimit))
+            ->take(10)
+            ->get(['id']);
+        if ($revisionsToDelete->count() > 0) {
+            PageRevision::query()->whereIn('id', $revisionsToDelete->pluck('id'))->delete();
+        }
     }
 
     /**
-     * Change the page's parent to the given entity.
-     * @param Page $page
-     * @param Entity $parent
-     * @throws \Throwable
+     * Get a new priority for a page
      */
-    public function changePageParent(Page $page, Entity $parent)
+    protected function getNewPriority(Page $page): int
     {
-        $book = $parent->isA('book') ? $parent : $parent->book;
-        $page->chapter_id = $parent->isA('chapter') ? $parent->id : 0;
-        $page->save();
-        if ($page->book->id !== $book->id) {
-            $page = $this->changeBook('page', $book->id, $page);
+        if ($page->parent() instanceof Chapter) {
+            $lastPage = $page->parent()->pages('desc')->first();
+            return $lastPage ? $lastPage->priority + 1 : 0;
         }
-        $page->load('book');
-        $this->permissionService->buildJointPermissionsForEntity($book);
+
+        return (new BookContents($page->book))->getLastPriority() + 1;
     }
 
     /**
-     * Create a copy of a page in a new location with a new name.
-     * @param \BookStack\Entities\Page $page
-     * @param \BookStack\Entities\Entity $newParent
-     * @param string $newName
-     * @return \BookStack\Entities\Page
-     * @throws \Throwable
+     * Get the query to find the user's draft copies of the given page.
      */
-    public function copyPage(Page $page, Entity $newParent, string $newName = '')
+    protected function getUserDraftQuery(Page $page)
     {
-        $newBook = $newParent->isA('book') ? $newParent : $newParent->book;
-        $newChapter = $newParent->isA('chapter') ? $newParent : null;
-        $copyPage = $this->getDraftPage($newBook, $newChapter);
-        $pageData = $page->getAttributes();
-
-        // Update name
-        if (!empty($newName)) {
-            $pageData['name'] = $newName;
-        }
-
-        // Copy tags from previous page if set
-        if ($page->tags) {
-            $pageData['tags'] = [];
-            foreach ($page->tags as $tag) {
-                $pageData['tags'][] = ['name' => $tag->name, 'value' => $tag->value];
-            }
-        }
-
-        // Set priority
-        if ($newParent->isA('chapter')) {
-            $pageData['priority'] = $this->getNewChapterPriority($newParent);
-        } else {
-            $pageData['priority'] = $this->getNewBookPriority($newParent);
-        }
-
-        return $this->publishPageDraft($copyPage, $pageData);
+        return PageRevision::query()->where('created_by', '=', user()->id)
+            ->where('type', 'update_draft')
+            ->where('page_id', '=', $page->id)
+            ->orderBy('created_at', 'desc');
     }
 }
index 9e7cfdd0cdf1c8de852ee88a5464b662580e6f5b..ee9b87786a57a1e059ed050621f0c694427b17cb 100644 (file)
@@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
 use Illuminate\Database\Query\Builder;
 use Illuminate\Database\Query\JoinClause;
 use Illuminate\Support\Collection;
+use Illuminate\Support\Str;
 
 class SearchService
 {
@@ -210,7 +211,7 @@ class SearchService
 
         // Handle filters
         foreach ($terms['filters'] as $filterTerm => $filterValue) {
-            $functionName = camel_case('filter_' . $filterTerm);
+            $functionName = Str::camel('filter_' . $filterTerm);
             if (method_exists($this, $functionName)) {
                 $this->$functionName($entitySelect, $entity, $filterValue);
             }
@@ -514,7 +515,7 @@ class SearchService
 
     protected function filterSortBy(EloquentBuilder $query, Entity $model, $input)
     {
-        $functionName = camel_case('sort_by_' . $input);
+        $functionName = Str::camel('sort_by_' . $input);
         if (method_exists($this, $functionName)) {
             $this->$functionName($query, $model);
         }
diff --git a/app/Entities/SlugGenerator.php b/app/Entities/SlugGenerator.php
new file mode 100644 (file)
index 0000000..459a526
--- /dev/null
@@ -0,0 +1,62 @@
+<?php namespace BookStack\Entities;
+
+class SlugGenerator
+{
+
+    protected $entity;
+
+    /**
+     * SlugGenerator constructor.
+     * @param $entity
+     */
+    public function __construct(Entity $entity)
+    {
+        $this->entity = $entity;
+    }
+
+    /**
+     * Generate a fresh slug for the given entity.
+     * The slug will generated so it does not conflict within the same parent item.
+     */
+    public function generate(): string
+    {
+        $slug = $this->formatNameAsSlug($this->entity->name);
+        while ($this->slugInUse($slug)) {
+            $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
+        }
+        return $slug;
+    }
+
+    /**
+     * Format a name as a url slug.
+     */
+    protected function formatNameAsSlug(string $name): string
+    {
+        $slug = preg_replace('/[\+\/\\\?\@\}\{\.\,\=\[\]\#\&\!\*\'\;\:\$\%]/', '', mb_strtolower($name));
+        $slug = preg_replace('/\s{2,}/', ' ', $slug);
+        $slug = str_replace(' ', '-', $slug);
+        if ($slug === "") {
+            $slug = substr(md5(rand(1, 500)), 0, 5);
+        }
+        return $slug;
+    }
+
+    /**
+     * Check if a slug is already in-use for this
+     * type of model within the same parent.
+     */
+    protected function slugInUse(string $slug): bool
+    {
+        $query = $this->entity->newQuery()->where('slug', '=', $slug);
+
+        if ($this->entity instanceof BookChild) {
+            $query->where('book_id', '=', $this->entity->book_id);
+        }
+
+        if ($this->entity->id) {
+            $query->where('id', '!=', $this->entity->id);
+        }
+
+        return $query->count() > 0;
+    }
+}
diff --git a/app/Exceptions/MoveOperationException.php b/app/Exceptions/MoveOperationException.php
new file mode 100644 (file)
index 0000000..c237dfa
--- /dev/null
@@ -0,0 +1,8 @@
+<?php namespace BookStack\Exceptions;
+
+use Exception;
+
+class MoveOperationException extends Exception
+{
+
+}
diff --git a/app/Exceptions/SortOperationException.php b/app/Exceptions/SortOperationException.php
new file mode 100644 (file)
index 0000000..8f91217
--- /dev/null
@@ -0,0 +1,8 @@
+<?php namespace BookStack\Exceptions;
+
+use Exception;
+
+class SortOperationException extends Exception
+{
+
+}
diff --git a/app/Exceptions/UserTokenExpiredException.php b/app/Exceptions/UserTokenExpiredException.php
new file mode 100644 (file)
index 0000000..e197074
--- /dev/null
@@ -0,0 +1,18 @@
+<?php namespace BookStack\Exceptions;
+
+class UserTokenExpiredException extends \Exception
+{
+
+    public $userId;
+
+    /**
+     * UserTokenExpiredException constructor.
+     * @param string $message
+     * @param int $userId
+     */
+    public function __construct(string $message, int $userId)
+    {
+        $this->userId = $userId;
+        parent::__construct($message);
+    }
+}
diff --git a/app/Exceptions/UserTokenNotFoundException.php b/app/Exceptions/UserTokenNotFoundException.php
new file mode 100644 (file)
index 0000000..3ed53f7
--- /dev/null
@@ -0,0 +1,6 @@
+<?php namespace BookStack\Exceptions;
+
+class UserTokenNotFoundException extends \Exception
+{
+
+}
diff --git a/app/Facades/Permissions.php b/app/Facades/Permissions.php
new file mode 100644 (file)
index 0000000..c552d7c
--- /dev/null
@@ -0,0 +1,16 @@
+<?php namespace BookStack\Facades;
+
+use Illuminate\Support\Facades\Facade;
+
+class Permissions extends Facade
+{
+    /**
+     * Get the registered name of the component.
+     *
+     * @return string
+     */
+    protected static function getFacadeAccessor()
+    {
+        return 'permissions';
+    }
+}
index 0289f8e1d8f09e5bdf1bde68c7437c2e5a4746be..8f5da49ed83c10c5979b9e4b8eaa74ac324e14b9 100644 (file)
@@ -1,37 +1,37 @@
 <?php namespace BookStack\Http\Controllers;
 
-use BookStack\Entities\Repos\EntityRepo;
+use BookStack\Entities\Repos\PageRepo;
 use BookStack\Exceptions\FileUploadException;
 use BookStack\Exceptions\NotFoundException;
 use BookStack\Uploads\Attachment;
 use BookStack\Uploads\AttachmentService;
+use Exception;
+use Illuminate\Contracts\Filesystem\FileNotFoundException;
 use Illuminate\Http\Request;
+use Illuminate\Validation\ValidationException;
 
 class AttachmentController extends Controller
 {
     protected $attachmentService;
     protected $attachment;
-    protected $entityRepo;
+    protected $pageRepo;
 
     /**
      * AttachmentController constructor.
-     * @param \BookStack\Uploads\AttachmentService $attachmentService
-     * @param Attachment $attachment
-     * @param EntityRepo $entityRepo
      */
-    public function __construct(AttachmentService $attachmentService, Attachment $attachment, EntityRepo $entityRepo)
+    public function __construct(AttachmentService $attachmentService, Attachment $attachment, PageRepo $pageRepo)
     {
         $this->attachmentService = $attachmentService;
         $this->attachment = $attachment;
-        $this->entityRepo = $entityRepo;
+        $this->pageRepo = $pageRepo;
         parent::__construct();
     }
 
 
     /**
      * Endpoint at which attachments are uploaded to.
-     * @param Request $request
-     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
+     * @throws ValidationException
+     * @throws NotFoundException
      */
     public function upload(Request $request)
     {
@@ -41,7 +41,7 @@ class AttachmentController extends Controller
         ]);
 
         $pageId = $request->get('uploaded_to');
-        $page = $this->entityRepo->getById('page', $pageId, true);
+        $page = $this->pageRepo->getById($pageId);
 
         $this->checkPermission('attachment-create-all');
         $this->checkOwnablePermission('page-update', $page);
@@ -59,11 +59,10 @@ class AttachmentController extends Controller
 
     /**
      * Update an uploaded attachment.
-     * @param int $attachmentId
-     * @param Request $request
-     * @return mixed
+     * @throws ValidationException
+     * @throws NotFoundException
      */
-    public function uploadUpdate($attachmentId, Request $request)
+    public function uploadUpdate(Request $request, $attachmentId)
     {
         $this->validate($request, [
             'uploaded_to' => 'required|integer|exists:pages,id',
@@ -71,7 +70,7 @@ class AttachmentController extends Controller
         ]);
 
         $pageId = $request->get('uploaded_to');
-        $page = $this->entityRepo->getById('page', $pageId, true);
+        $page = $this->pageRepo->getById($pageId);
         $attachment = $this->attachment->findOrFail($attachmentId);
 
         $this->checkOwnablePermission('page-update', $page);
@@ -94,11 +93,10 @@ class AttachmentController extends Controller
 
     /**
      * Update the details of an existing file.
-     * @param $attachmentId
-     * @param Request $request
-     * @return Attachment|mixed
+     * @throws ValidationException
+     * @throws NotFoundException
      */
-    public function update($attachmentId, Request $request)
+    public function update(Request $request, $attachmentId)
     {
         $this->validate($request, [
             'uploaded_to' => 'required|integer|exists:pages,id',
@@ -107,7 +105,7 @@ class AttachmentController extends Controller
         ]);
 
         $pageId = $request->get('uploaded_to');
-        $page = $this->entityRepo->getById('page', $pageId, true);
+        $page = $this->pageRepo->getById($pageId);
         $attachment = $this->attachment->findOrFail($attachmentId);
 
         $this->checkOwnablePermission('page-update', $page);
@@ -123,8 +121,8 @@ class AttachmentController extends Controller
 
     /**
      * Attach a link to a page.
-     * @param Request $request
-     * @return mixed
+     * @throws ValidationException
+     * @throws NotFoundException
      */
     public function attachLink(Request $request)
     {
@@ -135,7 +133,7 @@ class AttachmentController extends Controller
         ]);
 
         $pageId = $request->get('uploaded_to');
-        $page = $this->entityRepo->getById('page', $pageId, true);
+        $page = $this->pageRepo->getById($pageId);
 
         $this->checkPermission('attachment-create-all');
         $this->checkOwnablePermission('page-update', $page);
@@ -149,29 +147,26 @@ class AttachmentController extends Controller
 
     /**
      * Get the attachments for a specific page.
-     * @param $pageId
-     * @return mixed
      */
-    public function listForPage($pageId)
+    public function listForPage(int $pageId)
     {
-        $page = $this->entityRepo->getById('page', $pageId, true);
+        $page = $this->pageRepo->getById($pageId);
         $this->checkOwnablePermission('page-view', $page);
         return response()->json($page->attachments);
     }
 
     /**
      * Update the attachment sorting.
-     * @param $pageId
-     * @param Request $request
-     * @return mixed
+     * @throws ValidationException
+     * @throws NotFoundException
      */
-    public function sortForPage($pageId, Request $request)
+    public function sortForPage(Request $request, int $pageId)
     {
         $this->validate($request, [
             'files' => 'required|array',
             'files.*.id' => 'required|integer',
         ]);
-        $page = $this->entityRepo->getById('page', $pageId);
+        $page = $this->pageRepo->getById($pageId);
         $this->checkOwnablePermission('page-update', $page);
 
         $attachments = $request->get('files');
@@ -181,16 +176,15 @@ class AttachmentController extends Controller
 
     /**
      * Get an attachment from storage.
-     * @param $attachmentId
-     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Symfony\Component\HttpFoundation\Response
-     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
+     * @throws FileNotFoundException
      * @throws NotFoundException
      */
-    public function get($attachmentId)
+    public function get(int $attachmentId)
     {
         $attachment = $this->attachment->findOrFail($attachmentId);
-        $page = $this->entityRepo->getById('page', $attachment->uploaded_to);
-        if ($page === null) {
+        try {
+            $page = $this->pageRepo->getById($attachment->uploaded_to);
+        } catch (NotFoundException $exception) {
             throw new NotFoundException(trans('errors.attachment_not_found'));
         }
 
@@ -208,9 +202,9 @@ class AttachmentController extends Controller
      * Delete a specific attachment in the system.
      * @param $attachmentId
      * @return mixed
-     * @throws \Exception
+     * @throws Exception
      */
-    public function delete($attachmentId)
+    public function delete(int $attachmentId)
     {
         $attachment = $this->attachment->findOrFail($attachmentId);
         $this->checkOwnablePermission('attachment-delete', $attachment);
diff --git a/app/Http/Controllers/Auth/ConfirmEmailController.php b/app/Http/Controllers/Auth/ConfirmEmailController.php
new file mode 100644 (file)
index 0000000..099558e
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Auth\Access\EmailConfirmationService;
+use BookStack\Auth\UserRepo;
+use BookStack\Exceptions\ConfirmationEmailException;
+use BookStack\Exceptions\UserTokenExpiredException;
+use BookStack\Exceptions\UserTokenNotFoundException;
+use BookStack\Http\Controllers\Controller;
+use Exception;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Http\Request;
+use Illuminate\Routing\Redirector;
+use Illuminate\View\View;
+
+class ConfirmEmailController extends Controller
+{
+    protected $emailConfirmationService;
+    protected $userRepo;
+
+    /**
+     * Create a new controller instance.
+     *
+     * @param EmailConfirmationService $emailConfirmationService
+     * @param UserRepo $userRepo
+     */
+    public function __construct(EmailConfirmationService $emailConfirmationService, UserRepo $userRepo)
+    {
+        $this->emailConfirmationService = $emailConfirmationService;
+        $this->userRepo = $userRepo;
+        parent::__construct();
+    }
+
+
+    /**
+     * 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,
+     * Also has the option to re-send the confirmation email.
+     * @return View
+     */
+    public function showAwaiting()
+    {
+        return view('auth.user-unconfirmed');
+    }
+
+    /**
+     * Confirms an email via a token and logs the user into the system.
+     * @param $token
+     * @return RedirectResponse|Redirector
+     * @throws ConfirmationEmailException
+     * @throws Exception
+     */
+    public function confirm($token)
+    {
+        try {
+            $userId = $this->emailConfirmationService->checkTokenAndGetUserId($token);
+        } catch (Exception $exception) {
+            if ($exception instanceof UserTokenNotFoundException) {
+                $this->showErrorNotification(trans('errors.email_confirmation_invalid'));
+                return redirect('/register');
+            }
+
+            if ($exception instanceof UserTokenExpiredException) {
+                $user = $this->userRepo->getById($exception->userId);
+                $this->emailConfirmationService->sendConfirmation($user);
+                $this->showErrorNotification(trans('errors.email_confirmation_expired'));
+                return redirect('/register/confirm');
+            }
+
+            throw $exception;
+        }
+
+        $user = $this->userRepo->getById($userId);
+        $user->email_confirmed = true;
+        $user->save();
+
+        auth()->login($user);
+        $this->showSuccessNotification(trans('auth.email_confirm_success'));
+        $this->emailConfirmationService->deleteByUser($user);
+
+        return redirect('/');
+    }
+
+
+    /**
+     * Resend the confirmation email
+     * @param Request $request
+     * @return View
+     */
+    public function resend(Request $request)
+    {
+        $this->validate($request, [
+            'email' => 'required|email|exists:users,email'
+        ]);
+        $user = $this->userRepo->getByEmail($request->get('email'));
+
+        try {
+            $this->emailConfirmationService->sendConfirmation($user);
+        } catch (Exception $e) {
+            $this->showErrorNotification(trans('auth.email_confirm_send_error'));
+            return redirect('/register/confirm');
+        }
+
+        $this->showSuccessNotification(trans('auth.email_confirm_resent'));
+        return redirect('/register/confirm');
+    }
+}
index a0cbae9c611bffeb421e64788ebca7651ef2f65e..a3c0433a56f9e4af9d574ae0446edac02dbd5c1f 100644 (file)
@@ -53,7 +53,7 @@ class ForgotPasswordController extends Controller
 
         if ($response === Password::RESET_LINK_SENT) {
             $message = trans('auth.reset_password_sent_success', ['email' => $request->get('email')]);
-            session()->flash('success', $message);
+            $this->showSuccessNotification($message);
             return back()->with('status', trans($response));
         }
 
index 0a9537995c332bc1b0991607bfc90b15ed79663b..0cb050a89c2afd0e2425cec76bbef805ba3538f1 100644 (file)
@@ -98,6 +98,7 @@ class LoginController extends Controller
 
             $user->save();
             $this->userRepo->attachDefaultRole($user);
+            $this->userRepo->downloadAndAssignUserAvatar($user);
             auth()->login($user);
         }
 
index a285899ccb9cfc40eb911933591e18e2d6239df0..304d3bed2e69999b381e0a8e1b4cb6a22790f492 100644 (file)
@@ -18,7 +18,8 @@ use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Response;
 use Illuminate\Routing\Redirector;
-use Illuminate\View\View;
+use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Str;
 use Laravel\Socialite\Contracts\User as SocialUser;
 use Validator;
 
@@ -53,7 +54,7 @@ class RegisterController extends Controller
      * Create a new controller instance.
      *
      * @param SocialAuthService $socialAuthService
-     * @param \BookStack\Auth\EmailConfirmationService $emailConfirmationService
+     * @param EmailConfirmationService $emailConfirmationService
      * @param UserRepo $userRepo
      */
     public function __construct(SocialAuthService $socialAuthService, EmailConfirmationService $emailConfirmationService, UserRepo $userRepo)
@@ -78,7 +79,7 @@ class RegisterController extends Controller
         return Validator::make($data, [
             'name' => 'required|min:2|max:255',
             'email' => 'required|email|max:255|unique:users',
-            'password' => 'required|min:6',
+            'password' => 'required|min:8',
         ]);
     }
 
@@ -130,7 +131,7 @@ class RegisterController extends Controller
         return User::create([
             'name' => $data['name'],
             'email' => $data['email'],
-            'password' => bcrypt($data['password']),
+            'password' => Hash::make($data['password']),
         ]);
     }
 
@@ -159,83 +160,23 @@ class RegisterController extends Controller
             $newUser->socialAccounts()->save($socialAccount);
         }
 
-        if ((setting('registration-confirmation') || $registrationRestrict) && !$emailVerified) {
+        if ($this->emailConfirmationService->confirmationRequired() && !$emailVerified) {
             $newUser->save();
 
             try {
                 $this->emailConfirmationService->sendConfirmation($newUser);
             } catch (Exception $e) {
-                session()->flash('error', trans('auth.email_confirm_send_error'));
+                $this->showErrorNotification(trans('auth.email_confirm_send_error'));
             }
 
             return redirect('/register/confirm');
         }
 
         auth()->login($newUser);
-        session()->flash('success', trans('auth.register_success'));
+        $this->showSuccessNotification(trans('auth.register_success'));
         return redirect($this->redirectPath());
     }
 
-    /**
-     * Show the page to tell the user to check their email
-     * and confirm their address.
-     */
-    public function getRegisterConfirmation()
-    {
-        return view('auth.register-confirm');
-    }
-
-    /**
-     * Confirms an email via a token and logs the user into the system.
-     * @param $token
-     * @return RedirectResponse|Redirector
-     * @throws UserRegistrationException
-     */
-    public function confirmEmail($token)
-    {
-        $confirmation = $this->emailConfirmationService->getEmailConfirmationFromToken($token);
-        $user = $confirmation->user;
-        $user->email_confirmed = true;
-        $user->save();
-        auth()->login($user);
-        session()->flash('success', trans('auth.email_confirm_success'));
-        $this->emailConfirmationService->deleteConfirmationsByUser($user);
-        return redirect($this->redirectPath);
-    }
-
-    /**
-     * Shows a notice that a user's email address has not been confirmed,
-     * Also has the option to re-send the confirmation email.
-     * @return View
-     */
-    public function showAwaitingConfirmation()
-    {
-        return view('auth.user-unconfirmed');
-    }
-
-    /**
-     * Resend the confirmation email
-     * @param Request $request
-     * @return View
-     */
-    public function resendConfirmation(Request $request)
-    {
-        $this->validate($request, [
-            'email' => 'required|email|exists:users,email'
-        ]);
-        $user = $this->userRepo->getByEmail($request->get('email'));
-
-        try {
-            $this->emailConfirmationService->sendConfirmation($user);
-        } catch (Exception $e) {
-            session()->flash('error', trans('auth.email_confirm_send_error'));
-            return redirect('/register/confirm');
-        }
-
-        session()->flash('success', trans('auth.email_confirm_resent'));
-        return redirect('/register/confirm');
-    }
-
     /**
      * Redirect to the social site for authentication intended to register.
      * @param $socialDriver
@@ -252,14 +193,14 @@ class RegisterController extends Controller
 
     /**
      * The callback for social login services.
-     * @param $socialDriver
      * @param Request $request
+     * @param string $socialDriver
      * @return RedirectResponse|Redirector
      * @throws SocialSignInException
      * @throws UserRegistrationException
      * @throws SocialDriverNotConfigured
      */
-    public function socialCallback($socialDriver, Request $request)
+    public function socialCallback(Request $request, string $socialDriver)
     {
         if (!session()->has('social-callback')) {
             throw new SocialSignInException(trans('errors.social_no_action_defined'), '/login');
@@ -322,7 +263,7 @@ class RegisterController extends Controller
         $userData = [
             'name' => $socialUser->getName(),
             'email' => $socialUser->getEmail(),
-            'password' => str_random(30)
+            'password' => Str::random(30)
         ];
         return $this->registerUser($userData, $socialAccount, $emailVerified);
     }
index 56f1cf026f0727af67bc51d785c012bd6b74daa7..4d98eca597c3fffcece65fc79209bae646a49ab9 100644 (file)
@@ -4,6 +4,7 @@ namespace BookStack\Http\Controllers\Auth;
 
 use BookStack\Http\Controllers\Controller;
 use Illuminate\Foundation\Auth\ResetsPasswords;
+use Illuminate\Http\Request;
 
 class ResetPasswordController extends Controller
 {
@@ -36,13 +37,14 @@ class ResetPasswordController extends Controller
     /**
      * Get the response for a successful password reset.
      *
-     * @param  string  $response
+     * @param Request $request
+     * @param string $response
      * @return \Illuminate\Http\Response
      */
-    protected function sendResetResponse($response)
+    protected function sendResetResponse(Request $request, $response)
     {
         $message = trans('auth.reset_password_success');
-        session()->flash('success', $message);
+        $this->showSuccessNotification($message);
         return redirect($this->redirectPath())
             ->with('status', trans($response));
     }
diff --git a/app/Http/Controllers/Auth/UserInviteController.php b/app/Http/Controllers/Auth/UserInviteController.php
new file mode 100644 (file)
index 0000000..c361b0a
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Auth\Access\UserInviteService;
+use BookStack\Auth\UserRepo;
+use BookStack\Exceptions\UserTokenExpiredException;
+use BookStack\Exceptions\UserTokenNotFoundException;
+use BookStack\Http\Controllers\Controller;
+use Exception;
+use Illuminate\Contracts\View\Factory;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Http\Request;
+use Illuminate\Routing\Redirector;
+use Illuminate\View\View;
+
+class UserInviteController extends Controller
+{
+    protected $inviteService;
+    protected $userRepo;
+
+    /**
+     * Create a new controller instance.
+     *
+     * @param UserInviteService $inviteService
+     * @param UserRepo $userRepo
+     */
+    public function __construct(UserInviteService $inviteService, UserRepo $userRepo)
+    {
+        $this->inviteService = $inviteService;
+        $this->userRepo = $userRepo;
+        $this->middleware('guest');
+        parent::__construct();
+    }
+
+    /**
+     * Show the page for the user to set the password for their account.
+     * @param string $token
+     * @return Factory|View|RedirectResponse
+     * @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.
+     * @param Request $request
+     * @param string $token
+     * @return RedirectResponse|Redirector
+     * @throws Exception
+     */
+    public function setPassword(Request $request, string $token)
+    {
+        $this->validate($request, [
+            'password' => 'required|min:8'
+        ]);
+
+        try {
+            $userId = $this->inviteService->checkTokenAndGetUserId($token);
+        } catch (Exception $exception) {
+            return $this->handleTokenException($exception);
+        }
+
+        $user = $this->userRepo->getById($userId);
+        $user->password = bcrypt($request->get('password'));
+        $user->email_confirmed = true;
+        $user->save();
+
+        auth()->login($user);
+        $this->showSuccessNotification(trans('auth.user_invite_success', ['appName' => setting('app-name')]));
+        $this->inviteService->deleteByUser($user);
+
+        return redirect('/');
+    }
+
+    /**
+     * Check and validate the exception thrown when checking an invite token.
+     * @param Exception $exception
+     * @return RedirectResponse|Redirector
+     * @throws Exception
+     */
+    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;
+    }
+}
index a990eed9a950e4ffb45365bae858a35b279e1d43..e7d788d91dde01b78fdd158035fe07fd6cd5f54e 100644 (file)
@@ -1,67 +1,46 @@
 <?php namespace BookStack\Http\Controllers;
 
 use Activity;
-use BookStack\Auth\UserRepo;
-use BookStack\Entities\Book;
-use BookStack\Entities\EntityContextManager;
-use BookStack\Entities\Repos\EntityRepo;
-use BookStack\Entities\ExportService;
-use BookStack\Uploads\ImageRepo;
+use BookStack\Entities\Managers\BookContents;
+use BookStack\Entities\Bookshelf;
+use BookStack\Entities\Managers\EntityContext;
+use BookStack\Entities\Repos\BookRepo;
+use BookStack\Exceptions\ImageUploadException;
+use BookStack\Exceptions\NotifyException;
 use Illuminate\Http\Request;
-use Illuminate\Http\Response;
+use Illuminate\Validation\ValidationException;
+use Throwable;
 use Views;
 
 class BookController extends Controller
 {
 
-    protected $entityRepo;
-    protected $userRepo;
-    protected $exportService;
+    protected $bookRepo;
     protected $entityContextManager;
-    protected $imageRepo;
 
     /**
      * BookController constructor.
-     * @param EntityRepo $entityRepo
-     * @param UserRepo $userRepo
-     * @param ExportService $exportService
-     * @param EntityContextManager $entityContextManager
-     * @param ImageRepo $imageRepo
      */
-    public function __construct(
-        EntityRepo $entityRepo,
-        UserRepo $userRepo,
-        ExportService $exportService,
-        EntityContextManager $entityContextManager,
-        ImageRepo $imageRepo
-    ) {
-        $this->entityRepo = $entityRepo;
-        $this->userRepo = $userRepo;
-        $this->exportService = $exportService;
+    public function __construct(EntityContext $entityContextManager, BookRepo $bookRepo)
+    {
+        $this->bookRepo = $bookRepo;
         $this->entityContextManager = $entityContextManager;
-        $this->imageRepo = $imageRepo;
         parent::__construct();
     }
 
     /**
      * Display a listing of the book.
-     * @return Response
      */
     public function index()
     {
-        $view = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books'));
-        $sort = setting()->getUser($this->currentUser, 'books_sort', 'name');
-        $order = setting()->getUser($this->currentUser, 'books_sort_order', 'asc');
-        $sortOptions = [
-            'name' => trans('common.sort_name'),
-            'created_at' => trans('common.sort_created_at'),
-            'updated_at' => trans('common.sort_updated_at'),
-        ];
-
-        $books = $this->entityRepo->getAllPaginated('book', 18, $sort, $order);
-        $recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false;
-        $popular = $this->entityRepo->getPopular('book', 4, 0);
-        $new = $this->entityRepo->getRecentlyCreated('book', 4, 0);
+        $view = setting()->getForCurrentUser('books_view_type', config('app.views.books'));
+        $sort = setting()->getForCurrentUser('books_sort', 'name');
+        $order = setting()->getForCurrentUser('books_sort_order', 'asc');
+
+        $books = $this->bookRepo->getAllPaginated(18, $sort, $order);
+        $recents = $this->isSignedIn() ? $this->bookRepo->getRecentlyViewed(4) : false;
+        $popular = $this->bookRepo->getPopular(4);
+        $new = $this->bookRepo->getRecentlyCreated(4);
 
         $this->entityContextManager->clearShelfContext();
 
@@ -74,25 +53,22 @@ class BookController extends Controller
             'view' => $view,
             'sort' => $sort,
             'order' => $order,
-            'sortOptions' => $sortOptions,
         ]);
     }
 
     /**
      * Show the form for creating a new book.
-     * @param string $shelfSlug
-     * @return Response
-     * @throws \BookStack\Exceptions\NotFoundException
      */
     public function create(string $shelfSlug = null)
     {
+        $this->checkPermission('book-create-all');
+
         $bookshelf = null;
         if ($shelfSlug !== null) {
-            $bookshelf = $this->entityRepo->getBySlug('bookshelf', $shelfSlug);
+            $bookshelf = Bookshelf::visible()->where('slug', '=', $shelfSlug)->firstOrFail();
             $this->checkOwnablePermission('bookshelf-update', $bookshelf);
         }
 
-        $this->checkPermission('book-create-all');
         $this->setPageTitle(trans('entities.books_create'));
         return view('books.create', [
             'bookshelf' => $bookshelf
@@ -101,12 +77,8 @@ class BookController extends Controller
 
     /**
      * Store a newly created book in storage.
-     *
-     * @param Request $request
-     * @param string $shelfSlug
-     * @return Response
-     * @throws \BookStack\Exceptions\NotFoundException
-     * @throws \BookStack\Exceptions\ImageUploadException
+     * @throws ImageUploadException
+     * @throws ValidationException
      */
     public function store(Request $request, string $shelfSlug = null)
     {
@@ -114,21 +86,21 @@ class BookController extends Controller
         $this->validate($request, [
             'name' => 'required|string|max:255',
             'description' => 'string|max:1000',
-            'image' => $this->imageRepo->getImageValidationRules(),
+            'image' => $this->getImageValidationRules(),
         ]);
 
         $bookshelf = null;
         if ($shelfSlug !== null) {
-            $bookshelf = $this->entityRepo->getBySlug('bookshelf', $shelfSlug);
+            $bookshelf = Bookshelf::visible()->where('slug', '=', $shelfSlug)->firstOrFail();
             $this->checkOwnablePermission('bookshelf-update', $bookshelf);
         }
 
-        $book = $this->entityRepo->createFromInput('book', $request->all());
-        $this->bookUpdateActions($book, $request);
+        $book = $this->bookRepo->create($request->all());
+        $this->bookRepo->updateCoverImage($book, $request->file('image', null));
         Activity::add($book, 'book_create', $book->id);
 
         if ($bookshelf) {
-            $this->entityRepo->appendBookToShelf($bookshelf, $book);
+            $bookshelf->appendBook($book);
             Activity::add($bookshelf, 'bookshelf_update');
         }
 
@@ -137,17 +109,11 @@ class BookController extends Controller
 
     /**
      * Display the specified book.
-     * @param $slug
-     * @param Request $request
-     * @return Response
-     * @throws \BookStack\Exceptions\NotFoundException
      */
-    public function show($slug, Request $request)
+    public function show(Request $request, string $slug)
     {
-        $book = $this->entityRepo->getBySlug('book', $slug);
-        $this->checkOwnablePermission('book-view', $book);
-
-        $bookChildren = $this->entityRepo->getBookChildren($book);
+        $book = $this->bookRepo->getBySlug($slug);
+        $bookChildren = (new BookContents($book))->getTree(true);
 
         Views::add($book);
         if ($request->has('shelf')) {
@@ -165,12 +131,10 @@ class BookController extends Controller
 
     /**
      * Show the form for editing the specified book.
-     * @param $slug
-     * @return Response
      */
-    public function edit($slug)
+    public function edit(string $slug)
     {
-        $book = $this->entityRepo->getBySlug('book', $slug);
+        $book = $this->bookRepo->getBySlug($slug);
         $this->checkOwnablePermission('book-update', $book);
         $this->setPageTitle(trans('entities.books_edit_named', ['bookName'=>$book->getShortName()]));
         return view('books.edit', ['book' => $book, 'current' => $book]);
@@ -178,254 +142,83 @@ class BookController extends Controller
 
     /**
      * Update the specified book in storage.
-     * @param Request $request
-     * @param          $slug
-     * @return Response
-     * @throws \BookStack\Exceptions\ImageUploadException
-     * @throws \BookStack\Exceptions\NotFoundException
+     * @throws ImageUploadException
+     * @throws ValidationException
+     * @throws Throwable
      */
     public function update(Request $request, string $slug)
     {
-        $book = $this->entityRepo->getBySlug('book', $slug);
+        $book = $this->bookRepo->getBySlug($slug);
         $this->checkOwnablePermission('book-update', $book);
         $this->validate($request, [
             'name' => 'required|string|max:255',
             'description' => 'string|max:1000',
-            'image' => $this->imageRepo->getImageValidationRules(),
+            'image' => $this->getImageValidationRules(),
         ]);
 
-         $book = $this->entityRepo->updateFromInput('book', $book, $request->all());
-         $this->bookUpdateActions($book, $request);
+        $book = $this->bookRepo->update($book, $request->all());
+        $resetCover = $request->has('image_reset');
+        $this->bookRepo->updateCoverImage($book, $request->file('image', null), $resetCover);
 
-         Activity::add($book, 'book_update', $book->id);
+        Activity::add($book, 'book_update', $book->id);
 
-         return redirect($book->getUrl());
+        return redirect($book->getUrl());
     }
 
     /**
-     * Shows the page to confirm deletion
-     * @param $bookSlug
-     * @return \Illuminate\View\View
+     * Shows the page to confirm deletion.
      */
-    public function showDelete($bookSlug)
+    public function showDelete(string $bookSlug)
     {
-        $book = $this->entityRepo->getBySlug('book', $bookSlug);
+        $book = $this->bookRepo->getBySlug($bookSlug);
         $this->checkOwnablePermission('book-delete', $book);
-        $this->setPageTitle(trans('entities.books_delete_named', ['bookName'=>$book->getShortName()]));
+        $this->setPageTitle(trans('entities.books_delete_named', ['bookName' => $book->getShortName()]));
         return view('books.delete', ['book' => $book, 'current' => $book]);
     }
 
     /**
-     * Shows the view which allows pages to be re-ordered and sorted.
-     * @param string $bookSlug
-     * @return \Illuminate\View\View
-     * @throws \BookStack\Exceptions\NotFoundException
-     */
-    public function sort($bookSlug)
-    {
-        $book = $this->entityRepo->getBySlug('book', $bookSlug);
-        $this->checkOwnablePermission('book-update', $book);
-
-        $bookChildren = $this->entityRepo->getBookChildren($book, true);
-
-        $this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()]));
-        return view('books.sort', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]);
-    }
-
-    /**
-     * Shows the sort box for a single book.
-     * Used via AJAX when loading in extra books to a sort.
-     * @param $bookSlug
-     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     * Remove the specified book from the system.
+     * @throws Throwable
+     * @throws NotifyException
      */
-    public function getSortItem($bookSlug)
+    public function destroy(string $bookSlug)
     {
-        $book = $this->entityRepo->getBySlug('book', $bookSlug);
-        $bookChildren = $this->entityRepo->getBookChildren($book);
-        return view('books.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
-    }
-
-    /**
-     * Saves an array of sort mapping to pages and chapters.
-     * @param  string $bookSlug
-     * @param Request $request
-     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
-     */
-    public function saveSort($bookSlug, Request $request)
-    {
-        $book = $this->entityRepo->getBySlug('book', $bookSlug);
-        $this->checkOwnablePermission('book-update', $book);
-
-        // Return if no map sent
-        if (!$request->filled('sort-tree')) {
-            return redirect($book->getUrl());
-        }
-
-        // Sort pages and chapters
-        $sortMap = collect(json_decode($request->get('sort-tree')));
-        $bookIdsInvolved = collect([$book->id]);
-
-        // Load models into map
-        $sortMap->each(function ($mapItem) use ($bookIdsInvolved) {
-            $mapItem->type = ($mapItem->type === 'page' ? 'page' : 'chapter');
-            $mapItem->model = $this->entityRepo->getById($mapItem->type, $mapItem->id);
-            // Store source and target books
-            $bookIdsInvolved->push(intval($mapItem->model->book_id));
-            $bookIdsInvolved->push(intval($mapItem->book));
-        });
-
-        // Get the books involved in the sort
-        $bookIdsInvolved = $bookIdsInvolved->unique()->toArray();
-        $booksInvolved = $this->entityRepo->getManyById('book', $bookIdsInvolved, false, true);
-        // Throw permission error if invalid ids or inaccessible books given.
-        if (count($bookIdsInvolved) !== count($booksInvolved)) {
-            $this->showPermissionError();
-        }
-        // Check permissions of involved books
-        $booksInvolved->each(function (Book $book) {
-             $this->checkOwnablePermission('book-update', $book);
-        });
-
-        // Perform the sort
-        $sortMap->each(function ($mapItem) {
-            $model = $mapItem->model;
-
-            $priorityChanged = intval($model->priority) !== intval($mapItem->sort);
-            $bookChanged = intval($model->book_id) !== intval($mapItem->book);
-            $chapterChanged = ($mapItem->type === 'page') && intval($model->chapter_id) !== $mapItem->parentChapter;
-
-            if ($bookChanged) {
-                $this->entityRepo->changeBook($mapItem->type, $mapItem->book, $model);
-            }
-            if ($chapterChanged) {
-                $model->chapter_id = intval($mapItem->parentChapter);
-                $model->save();
-            }
-            if ($priorityChanged) {
-                $model->priority = intval($mapItem->sort);
-                $model->save();
-            }
-        });
-
-        // Rebuild permissions and add activity for involved books.
-        $booksInvolved->each(function (Book $book) {
-            $this->entityRepo->buildJointPermissionsForBook($book);
-            Activity::add($book, 'book_sort', $book->id);
-        });
-
-        return redirect($book->getUrl());
-    }
-
-    /**
-     * Remove the specified book from storage.
-     * @param $bookSlug
-     * @return Response
-     */
-    public function destroy($bookSlug)
-    {
-        $book = $this->entityRepo->getBySlug('book', $bookSlug);
+        $book = $this->bookRepo->getBySlug($bookSlug);
         $this->checkOwnablePermission('book-delete', $book);
-        Activity::addMessage('book_delete', 0, $book->name);
 
-        if ($book->cover) {
-            $this->imageRepo->destroyImage($book->cover);
-        }
-        $this->entityRepo->destroyBook($book);
+        Activity::addMessage('book_delete', $book->name);
+        $this->bookRepo->destroy($book);
 
         return redirect('/books');
     }
 
     /**
-     * Show the Restrictions view.
-     * @param $bookSlug
-     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     * Show the permissions view.
      */
-    public function showPermissions($bookSlug)
+    public function showPermissions(string $bookSlug)
     {
-        $book = $this->entityRepo->getBySlug('book', $bookSlug);
+        $book = $this->bookRepo->getBySlug($bookSlug);
         $this->checkOwnablePermission('restrictions-manage', $book);
-        $roles = $this->userRepo->getRestrictableRoles();
+
         return view('books.permissions', [
             'book' => $book,
-            'roles' => $roles
         ]);
     }
 
     /**
      * Set the restrictions for this book.
-     * @param $bookSlug
-     * @param Request $request
-     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
-     * @throws \BookStack\Exceptions\NotFoundException
-     * @throws \Throwable
+     * @throws Throwable
      */
-    public function permissions($bookSlug, Request $request)
+    public function permissions(Request $request, string $bookSlug)
     {
-        $book = $this->entityRepo->getBySlug('book', $bookSlug);
+        $book = $this->bookRepo->getBySlug($bookSlug);
         $this->checkOwnablePermission('restrictions-manage', $book);
-        $this->entityRepo->updateEntityPermissionsFromRequest($request, $book);
-        session()->flash('success', trans('entities.books_permissions_updated'));
-        return redirect($book->getUrl());
-    }
-
-    /**
-     * Export a book as a PDF file.
-     * @param string $bookSlug
-     * @return mixed
-     */
-    public function exportPdf($bookSlug)
-    {
-        $book = $this->entityRepo->getBySlug('book', $bookSlug);
-        $pdfContent = $this->exportService->bookToPdf($book);
-        return $this->downloadResponse($pdfContent, $bookSlug . '.pdf');
-    }
-
-    /**
-     * Export a book as a contained HTML file.
-     * @param string $bookSlug
-     * @return mixed
-     */
-    public function exportHtml($bookSlug)
-    {
-        $book = $this->entityRepo->getBySlug('book', $bookSlug);
-        $htmlContent = $this->exportService->bookToContainedHtml($book);
-        return $this->downloadResponse($htmlContent, $bookSlug . '.html');
-    }
-
-    /**
-     * Export a book as a plain text file.
-     * @param $bookSlug
-     * @return mixed
-     */
-    public function exportPlainText($bookSlug)
-    {
-        $book = $this->entityRepo->getBySlug('book', $bookSlug);
-        $textContent = $this->exportService->bookToPlainText($book);
-        return $this->downloadResponse($textContent, $bookSlug . '.txt');
-    }
 
-    /**
-     * Common actions to run on book update.
-     * Handles updating the cover image.
-     * @param Book $book
-     * @param Request $request
-     * @throws \BookStack\Exceptions\ImageUploadException
-     */
-    protected function bookUpdateActions(Book $book, Request $request)
-    {
-        // Update the cover image if in request
-        if ($request->has('image')) {
-            $this->imageRepo->destroyImage($book->cover);
-            $newImage = $request->file('image');
-            $image = $this->imageRepo->saveNew($newImage, 'cover_book', $book->id, 512, 512, true);
-            $book->image_id = $image->id;
-            $book->save();
-        }
+        $restricted = $request->get('restricted') === 'true';
+        $permissions = $request->filled('restrictions') ? collect($request->get('restrictions')) : null;
+        $this->bookRepo->updatePermissions($book, $restricted, $permissions);
 
-        if ($request->has('image_reset')) {
-            $this->imageRepo->destroyImage($book->cover);
-            $book->image_id = 0;
-            $book->save();
-        }
+        $this->showSuccessNotification(trans('entities.books_permissions_updated'));
+        return redirect($book->getUrl());
     }
 }
diff --git a/app/Http/Controllers/BookExportController.php b/app/Http/Controllers/BookExportController.php
new file mode 100644 (file)
index 0000000..cfa3d6a
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+namespace BookStack\Http\Controllers;
+
+use BookStack\Entities\ExportService;
+use BookStack\Entities\Repos\BookRepo;
+use Throwable;
+
+class BookExportController extends Controller
+{
+
+    protected $bookRepo;
+    protected $exportService;
+
+    /**
+     * BookExportController constructor.
+     */
+    public function __construct(BookRepo $bookRepo, ExportService $exportService)
+    {
+        $this->bookRepo = $bookRepo;
+        $this->exportService = $exportService;
+        parent::__construct();
+    }
+
+    /**
+     * Export a book as a PDF file.
+     * @throws Throwable
+     */
+    public function pdf(string $bookSlug)
+    {
+        $book = $this->bookRepo->getBySlug($bookSlug);
+        $pdfContent = $this->exportService->bookToPdf($book);
+        return $this->downloadResponse($pdfContent, $bookSlug . '.pdf');
+    }
+
+    /**
+     * Export a book as a contained HTML file.
+     * @throws Throwable
+     */
+    public function html(string $bookSlug)
+    {
+        $book = $this->bookRepo->getBySlug($bookSlug);
+        $htmlContent = $this->exportService->bookToContainedHtml($book);
+        return $this->downloadResponse($htmlContent, $bookSlug . '.html');
+    }
+
+    /**
+     * Export a book as a plain text file.
+     */
+    public function plainText(string $bookSlug)
+    {
+        $book = $this->bookRepo->getBySlug($bookSlug);
+        $textContent = $this->exportService->bookToPlainText($book);
+        return $this->downloadResponse($textContent, $bookSlug . '.txt');
+    }
+}
diff --git a/app/Http/Controllers/BookSortController.php b/app/Http/Controllers/BookSortController.php
new file mode 100644 (file)
index 0000000..f5fb6f2
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+namespace BookStack\Http\Controllers;
+
+use BookStack\Entities\Book;
+use BookStack\Entities\Managers\BookContents;
+use BookStack\Entities\Repos\BookRepo;
+use BookStack\Exceptions\SortOperationException;
+use BookStack\Facades\Activity;
+use Illuminate\Http\Request;
+
+class BookSortController extends Controller
+{
+
+    protected $bookRepo;
+
+    /**
+     * BookSortController constructor.
+     * @param $bookRepo
+     */
+    public function __construct(BookRepo $bookRepo)
+    {
+        $this->bookRepo = $bookRepo;
+        parent::__construct();
+    }
+
+    /**
+     * Shows the view which allows pages to be re-ordered and sorted.
+     */
+    public function show(string $bookSlug)
+    {
+        $book = $this->bookRepo->getBySlug($bookSlug);
+        $this->checkOwnablePermission('book-update', $book);
+
+        $bookChildren = (new BookContents($book))->getTree(false);
+
+        $this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()]));
+        return view('books.sort', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]);
+    }
+
+    /**
+     * Shows the sort box for a single book.
+     * Used via AJAX when loading in extra books to a sort.
+     */
+    public function showItem(string $bookSlug)
+    {
+        $book = $this->bookRepo->getBySlug($bookSlug);
+        $bookChildren = (new BookContents($book))->getTree();
+        return view('books.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
+    }
+
+    /**
+     * Sorts a book using a given mapping array.
+     */
+    public function update(Request $request, string $bookSlug)
+    {
+        $book = $this->bookRepo->getBySlug($bookSlug);
+        $this->checkOwnablePermission('book-update', $book);
+
+        // Return if no map sent
+        if (!$request->filled('sort-tree')) {
+            return redirect($book->getUrl());
+        }
+
+        $sortMap = collect(json_decode($request->get('sort-tree')));
+        $bookContents = new BookContents($book);
+        $booksInvolved = collect();
+
+        try {
+            $booksInvolved = $bookContents->sortUsingMap($sortMap);
+        } catch (SortOperationException $exception) {
+            $this->showPermissionError();
+        }
+
+        // Rebuild permissions and add activity for involved books.
+        $booksInvolved->each(function (Book $book) {
+            Activity::add($book, 'book_sort', $book->id);
+        });
+
+        return redirect($book->getUrl());
+    }
+}
index bcf2e12df88a67628bdded31f2db1162ce120098..57e67dc00e618b53fec9b16eec134242422dd8af 100644 (file)
@@ -1,34 +1,30 @@
 <?php namespace BookStack\Http\Controllers;
 
 use Activity;
-use BookStack\Auth\UserRepo;
-use BookStack\Entities\Bookshelf;
-use BookStack\Entities\EntityContextManager;
-use BookStack\Entities\Repos\EntityRepo;
+use BookStack\Entities\Book;
+use BookStack\Entities\Managers\EntityContext;
+use BookStack\Entities\Repos\BookshelfRepo;
+use BookStack\Exceptions\ImageUploadException;
+use BookStack\Exceptions\NotFoundException;
 use BookStack\Uploads\ImageRepo;
+use Exception;
 use Illuminate\Http\Request;
-use Illuminate\Http\Response;
+use Illuminate\Validation\ValidationException;
 use Views;
 
 class BookshelfController extends Controller
 {
 
-    protected $entityRepo;
-    protected $userRepo;
+    protected $bookshelfRepo;
     protected $entityContextManager;
     protected $imageRepo;
 
     /**
      * BookController constructor.
-     * @param EntityRepo $entityRepo
-     * @param UserRepo $userRepo
-     * @param EntityContextManager $entityContextManager
-     * @param ImageRepo $imageRepo
      */
-    public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, EntityContextManager $entityContextManager, ImageRepo $imageRepo)
+    public function __construct(BookshelfRepo $bookshelfRepo, EntityContext $entityContextManager, ImageRepo $imageRepo)
     {
-        $this->entityRepo = $entityRepo;
-        $this->userRepo = $userRepo;
+        $this->bookshelfRepo = $bookshelfRepo;
         $this->entityContextManager = $entityContextManager;
         $this->imageRepo = $imageRepo;
         parent::__construct();
@@ -36,27 +32,22 @@ class BookshelfController extends Controller
 
     /**
      * Display a listing of the book.
-     * @return Response
      */
     public function index()
     {
-        $view = setting()->getUser($this->currentUser, 'bookshelves_view_type', config('app.views.bookshelves', 'grid'));
-        $sort = setting()->getUser($this->currentUser, 'bookshelves_sort', 'name');
-        $order = setting()->getUser($this->currentUser, 'bookshelves_sort_order', 'asc');
+        $view = setting()->getForCurrentUser('bookshelves_view_type', config('app.views.bookshelves', 'grid'));
+        $sort = setting()->getForCurrentUser('bookshelves_sort', 'name');
+        $order = setting()->getForCurrentUser('bookshelves_sort_order', 'asc');
         $sortOptions = [
             'name' => trans('common.sort_name'),
             'created_at' => trans('common.sort_created_at'),
             'updated_at' => trans('common.sort_updated_at'),
         ];
 
-        $shelves = $this->entityRepo->getAllPaginated('bookshelf', 18, $sort, $order);
-        foreach ($shelves as $shelf) {
-            $shelf->books = $this->entityRepo->getBookshelfChildren($shelf);
-        }
-
-        $recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('bookshelf', 4, 0) : false;
-        $popular = $this->entityRepo->getPopular('bookshelf', 4, 0);
-        $new = $this->entityRepo->getRecentlyCreated('bookshelf', 4, 0);
+        $shelves = $this->bookshelfRepo->getAllPaginated(18, $sort, $order);
+        $recents = $this->isSignedIn() ? $this->bookshelfRepo->getRecentlyViewed(4) : false;
+        $popular = $this->bookshelfRepo->getPopular(4);
+        $new = $this->bookshelfRepo->getRecentlyCreated(4);
 
         $this->entityContextManager->clearShelfContext();
         $this->setPageTitle(trans('entities.shelves'));
@@ -74,21 +65,19 @@ class BookshelfController extends Controller
 
     /**
      * Show the form for creating a new bookshelf.
-     * @return Response
      */
     public function create()
     {
         $this->checkPermission('bookshelf-create-all');
-        $books = $this->entityRepo->getAll('book', false, 'update');
+        $books = Book::hasPermission('update')->get();
         $this->setPageTitle(trans('entities.shelves_create'));
         return view('shelves.create', ['books' => $books]);
     }
 
     /**
      * Store a newly created bookshelf in storage.
-     * @param Request $request
-     * @return Response
-     * @throws \BookStack\Exceptions\ImageUploadException
+     * @throws ValidationException
+     * @throws ImageUploadException
      */
     public function store(Request $request)
     {
@@ -96,80 +85,63 @@ class BookshelfController extends Controller
         $this->validate($request, [
             'name' => 'required|string|max:255',
             'description' => 'string|max:1000',
-            'image' => $this->imageRepo->getImageValidationRules(),
+            'image' => $this->getImageValidationRules(),
         ]);
 
-        $shelf = $this->entityRepo->createFromInput('bookshelf', $request->all());
-        $this->shelfUpdateActions($shelf, $request);
+        $bookIds = explode(',', $request->get('books', ''));
+        $shelf = $this->bookshelfRepo->create($request->all(), $bookIds);
+        $this->bookshelfRepo->updateCoverImage($shelf);
 
         Activity::add($shelf, 'bookshelf_create');
         return redirect($shelf->getUrl());
     }
 
-
     /**
-     * Display the specified bookshelf.
-     * @param String $slug
-     * @return Response
-     * @throws \BookStack\Exceptions\NotFoundException
+     * Display the bookshelf of the given slug.
+     * @throws NotFoundException
      */
     public function show(string $slug)
     {
-        /** @var Bookshelf $shelf */
-        $shelf = $this->entityRepo->getBySlug('bookshelf', $slug);
+        $shelf = $this->bookshelfRepo->getBySlug($slug);
         $this->checkOwnablePermission('book-view', $shelf);
 
-        $books = $this->entityRepo->getBookshelfChildren($shelf);
         Views::add($shelf);
         $this->entityContextManager->setShelfContext($shelf->id);
 
         $this->setPageTitle($shelf->getShortName());
-
         return view('shelves.show', [
             'shelf' => $shelf,
-            'books' => $books,
             'activity' => Activity::entityActivity($shelf, 20, 1)
         ]);
     }
 
     /**
      * Show the form for editing the specified bookshelf.
-     * @param $slug
-     * @return Response
-     * @throws \BookStack\Exceptions\NotFoundException
      */
     public function edit(string $slug)
     {
-        $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */
+        $shelf = $this->bookshelfRepo->getBySlug($slug);
         $this->checkOwnablePermission('bookshelf-update', $shelf);
 
-        $shelfBooks = $this->entityRepo->getBookshelfChildren($shelf);
-        $shelfBookIds = $shelfBooks->pluck('id');
-        $books = $this->entityRepo->getAll('book', false, 'update');
-        $books = $books->filter(function ($book) use ($shelfBookIds) {
-             return !$shelfBookIds->contains($book->id);
-        });
+        $shelfBookIds = $shelf->books()->get(['id'])->pluck('id');
+        $books = Book::hasPermission('update')->whereNotIn('id', $shelfBookIds)->get();
 
         $this->setPageTitle(trans('entities.shelves_edit_named', ['name' => $shelf->getShortName()]));
         return view('shelves.edit', [
             'shelf' => $shelf,
             'books' => $books,
-            'shelfBooks' => $shelfBooks,
         ]);
     }
 
-
     /**
      * Update the specified bookshelf in storage.
-     * @param Request $request
-     * @param string $slug
-     * @return Response
-     * @throws \BookStack\Exceptions\NotFoundException
-     * @throws \BookStack\Exceptions\ImageUploadException
+     * @throws ValidationException
+     * @throws ImageUploadException
+     * @throws NotFoundException
      */
     public function update(Request $request, string $slug)
     {
-        $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */
+        $shelf = $this->bookshelfRepo->getBySlug($slug);
         $this->checkOwnablePermission('bookshelf-update', $shelf);
         $this->validate($request, [
             'name' => 'required|string|max:255',
@@ -177,24 +149,22 @@ class BookshelfController extends Controller
             'image' => $this->imageRepo->getImageValidationRules(),
         ]);
 
-         $shelf = $this->entityRepo->updateFromInput('bookshelf', $shelf, $request->all());
-         $this->shelfUpdateActions($shelf, $request);
 
-         Activity::add($shelf, 'bookshelf_update');
+        $bookIds = explode(',', $request->get('books', ''));
+        $shelf = $this->bookshelfRepo->update($shelf, $request->all(), $bookIds);
+        $resetCover = $request->has('image_reset');
+        $this->bookshelfRepo->updateCoverImage($shelf, $request->file('image', null), $resetCover);
+        Activity::add($shelf, 'bookshelf_update');
 
-         return redirect($shelf->getUrl());
+        return redirect($shelf->getUrl());
     }
 
-
     /**
      * Shows the page to confirm deletion
-     * @param $slug
-     * @return \Illuminate\View\View
-     * @throws \BookStack\Exceptions\NotFoundException
      */
     public function showDelete(string $slug)
     {
-        $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */
+        $shelf = $this->bookshelfRepo->getBySlug($slug);
         $this->checkOwnablePermission('bookshelf-delete', $shelf);
 
         $this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $shelf->getShortName()]));
@@ -203,101 +173,58 @@ class BookshelfController extends Controller
 
     /**
      * Remove the specified bookshelf from storage.
-     * @param string $slug
-     * @return Response
-     * @throws \BookStack\Exceptions\NotFoundException
-     * @throws \Throwable
+     * @throws Exception
      */
     public function destroy(string $slug)
     {
-        $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */
+        $shelf = $this->bookshelfRepo->getBySlug($slug);
         $this->checkOwnablePermission('bookshelf-delete', $shelf);
-        Activity::addMessage('bookshelf_delete', 0, $shelf->name);
 
-        if ($shelf->cover) {
-            $this->imageRepo->destroyImage($shelf->cover);
-        }
-        $this->entityRepo->destroyBookshelf($shelf);
+        Activity::addMessage('bookshelf_delete', $shelf->name);
+        $this->bookshelfRepo->destroy($shelf);
 
         return redirect('/shelves');
     }
 
     /**
      * Show the permissions view.
-     * @param string $slug
-     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
-     * @throws \BookStack\Exceptions\NotFoundException
      */
     public function showPermissions(string $slug)
     {
-        $shelf = $this->entityRepo->getBySlug('bookshelf', $slug);
+        $shelf = $this->bookshelfRepo->getBySlug($slug);
         $this->checkOwnablePermission('restrictions-manage', $shelf);
 
-        $roles = $this->userRepo->getRestrictableRoles();
         return view('shelves.permissions', [
             'shelf' => $shelf,
-            'roles' => $roles
         ]);
     }
 
     /**
      * Set the permissions for this bookshelf.
-     * @param string $slug
-     * @param Request $request
-     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
-     * @throws \BookStack\Exceptions\NotFoundException
-     * @throws \Throwable
      */
-    public function permissions(string $slug, Request $request)
+    public function permissions(Request $request, string $slug)
     {
-        $shelf = $this->entityRepo->getBySlug('bookshelf', $slug);
+        $shelf = $this->bookshelfRepo->getBySlug($slug);
         $this->checkOwnablePermission('restrictions-manage', $shelf);
 
-        $this->entityRepo->updateEntityPermissionsFromRequest($request, $shelf);
-        session()->flash('success', trans('entities.shelves_permissions_updated'));
+        $restricted = $request->get('restricted') === 'true';
+        $permissions = $request->filled('restrictions') ? collect($request->get('restrictions')) : null;
+        $this->bookshelfRepo->updatePermissions($shelf, $restricted, $permissions);
+
+        $this->showSuccessNotification(trans('entities.shelves_permissions_updated'));
         return redirect($shelf->getUrl());
     }
 
     /**
      * Copy the permissions of a bookshelf to the child books.
-     * @param string $slug
-     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
-     * @throws \BookStack\Exceptions\NotFoundException
      */
     public function copyPermissions(string $slug)
     {
-        $shelf = $this->entityRepo->getBySlug('bookshelf', $slug);
+        $shelf = $this->bookshelfRepo->getBySlug($slug);
         $this->checkOwnablePermission('restrictions-manage', $shelf);
 
-        $updateCount = $this->entityRepo->copyBookshelfPermissions($shelf);
-        session()->flash('success', trans('entities.shelves_copy_permission_success', ['count' => $updateCount]));
+        $updateCount = $this->bookshelfRepo->copyDownPermissions($shelf);
+        $this->showSuccessNotification(trans('entities.shelves_copy_permission_success', ['count' => $updateCount]));
         return redirect($shelf->getUrl());
     }
-
-    /**
-     * Common actions to run on bookshelf update.
-     * @param Bookshelf $shelf
-     * @param Request $request
-     * @throws \BookStack\Exceptions\ImageUploadException
-     */
-    protected function shelfUpdateActions(Bookshelf $shelf, Request $request)
-    {
-        // Update the books that the shelf references
-        $this->entityRepo->updateShelfBooks($shelf, $request->get('books', ''));
-
-        // Update the cover image if in request
-        if ($request->has('image')) {
-            $newImage = $request->file('image');
-            $this->imageRepo->destroyImage($shelf->cover);
-            $image = $this->imageRepo->saveNew($newImage, 'cover_shelf', $shelf->id, 512, 512, true);
-            $shelf->image_id = $image->id;
-            $shelf->save();
-        }
-
-        if ($request->has('image_reset')) {
-            $this->imageRepo->destroyImage($shelf->cover);
-            $shelf->image_id = 0;
-            $shelf->save();
-        }
-    }
 }
index c19e45694850f7418ca8f5c98c5ae15a024847f9..1355979107eb0181d272e3610511688d5772b7b7 100644 (file)
@@ -1,83 +1,74 @@
 <?php namespace BookStack\Http\Controllers;
 
 use Activity;
-use BookStack\Auth\UserRepo;
-use BookStack\Entities\Repos\EntityRepo;
-use BookStack\Entities\ExportService;
+use BookStack\Entities\Book;
+use BookStack\Entities\Managers\BookContents;
+use BookStack\Entities\Repos\ChapterRepo;
+use BookStack\Exceptions\MoveOperationException;
+use BookStack\Exceptions\NotFoundException;
 use Illuminate\Http\Request;
-use Illuminate\Http\Response;
+use Illuminate\Validation\ValidationException;
+use Throwable;
 use Views;
 
 class ChapterController extends Controller
 {
 
-    protected $userRepo;
-    protected $entityRepo;
-    protected $exportService;
+    protected $chapterRepo;
 
     /**
      * ChapterController constructor.
-     * @param EntityRepo $entityRepo
-     * @param UserRepo $userRepo
-     * @param \BookStack\Entities\ExportService $exportService
      */
-    public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
+    public function __construct(ChapterRepo $chapterRepo)
     {
-        $this->entityRepo = $entityRepo;
-        $this->userRepo = $userRepo;
-        $this->exportService = $exportService;
+        $this->chapterRepo = $chapterRepo;
         parent::__construct();
     }
 
     /**
      * Show the form for creating a new chapter.
-     * @param $bookSlug
-     * @return Response
      */
-    public function create($bookSlug)
+    public function create(string $bookSlug)
     {
-        $book = $this->entityRepo->getBySlug('book', $bookSlug);
+        $book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail();
         $this->checkOwnablePermission('chapter-create', $book);
+
         $this->setPageTitle(trans('entities.chapters_create'));
         return view('chapters.create', ['book' => $book, 'current' => $book]);
     }
 
     /**
      * Store a newly created chapter in storage.
-     * @param          $bookSlug
-     * @param  Request $request
-     * @return Response
+     * @throws ValidationException
      */
-    public function store($bookSlug, Request $request)
+    public function store(Request $request, string $bookSlug)
     {
         $this->validate($request, [
             'name' => 'required|string|max:255'
         ]);
 
-        $book = $this->entityRepo->getBySlug('book', $bookSlug);
+        $book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail();
         $this->checkOwnablePermission('chapter-create', $book);
 
-        $input = $request->all();
-        $input['priority'] = $this->entityRepo->getNewBookPriority($book);
-        $chapter = $this->entityRepo->createFromInput('chapter', $input, $book);
+        $chapter = $this->chapterRepo->create($request->all(), $book);
         Activity::add($chapter, 'chapter_create', $book->id);
+
         return redirect($chapter->getUrl());
     }
 
     /**
      * Display the specified chapter.
-     * @param $bookSlug
-     * @param $chapterSlug
-     * @return Response
      */
-    public function show($bookSlug, $chapterSlug)
+    public function show(string $bookSlug, string $chapterSlug)
     {
-        $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $this->checkOwnablePermission('chapter-view', $chapter);
-        $sidebarTree = $this->entityRepo->getBookChildren($chapter->book);
+
+        $sidebarTree = (new BookContents($chapter->book))->getTree();
+        $pages = $chapter->getVisiblePages();
         Views::add($chapter);
+
         $this->setPageTitle($chapter->getShortName());
-        $pages = $this->entityRepo->getChapterChildren($chapter);
         return view('chapters.show', [
             'book' => $chapter->book,
             'chapter' => $chapter,
@@ -89,79 +80,71 @@ class ChapterController extends Controller
 
     /**
      * Show the form for editing the specified chapter.
-     * @param $bookSlug
-     * @param $chapterSlug
-     * @return Response
      */
-    public function edit($bookSlug, $chapterSlug)
+    public function edit(string $bookSlug, string $chapterSlug)
     {
-        $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $this->checkOwnablePermission('chapter-update', $chapter);
+
         $this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()]));
         return view('chapters.edit', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
     }
 
     /**
      * Update the specified chapter in storage.
-     * @param  Request $request
-     * @param          $bookSlug
-     * @param          $chapterSlug
-     * @return Response
-     * @throws \BookStack\Exceptions\NotFoundException
+     * @throws NotFoundException
      */
-    public function update(Request $request, $bookSlug, $chapterSlug)
+    public function update(Request $request, string $bookSlug, string $chapterSlug)
     {
-        $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $this->checkOwnablePermission('chapter-update', $chapter);
 
-        $this->entityRepo->updateFromInput('chapter', $chapter, $request->all());
+        $this->chapterRepo->update($chapter, $request->all());
         Activity::add($chapter, 'chapter_update', $chapter->book->id);
+
         return redirect($chapter->getUrl());
     }
 
     /**
      * Shows the page to confirm deletion of this chapter.
-     * @param $bookSlug
-     * @param $chapterSlug
-     * @return \Illuminate\View\View
+     * @throws NotFoundException
      */
-    public function showDelete($bookSlug, $chapterSlug)
+    public function showDelete(string $bookSlug, string $chapterSlug)
     {
-        $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $this->checkOwnablePermission('chapter-delete', $chapter);
+
         $this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()]));
         return view('chapters.delete', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
     }
 
     /**
      * Remove the specified chapter from storage.
-     * @param $bookSlug
-     * @param $chapterSlug
-     * @return Response
+     * @throws NotFoundException
+     * @throws Throwable
      */
-    public function destroy($bookSlug, $chapterSlug)
+    public function destroy(string $bookSlug, string $chapterSlug)
     {
-        $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
-        $book = $chapter->book;
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $this->checkOwnablePermission('chapter-delete', $chapter);
-        Activity::addMessage('chapter_delete', $book->id, $chapter->name);
-        $this->entityRepo->destroyChapter($chapter);
-        return redirect($book->getUrl());
+
+        Activity::addMessage('chapter_delete', $chapter->name, $chapter->book->id);
+        $this->chapterRepo->destroy($chapter);
+
+        return redirect($chapter->book->getUrl());
     }
 
     /**
      * Show the page for moving a chapter.
-     * @param $bookSlug
-     * @param $chapterSlug
-     * @return mixed
-     * @throws \BookStack\Exceptions\NotFoundException
+     * @throws NotFoundException
      */
-    public function showMove($bookSlug, $chapterSlug)
+    public function showMove(string $bookSlug, string $chapterSlug)
     {
-        $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()]));
         $this->checkOwnablePermission('chapter-update', $chapter);
         $this->checkOwnablePermission('chapter-delete', $chapter);
+
         return view('chapters.move', [
             'chapter' => $chapter,
             'book' => $chapter->book
@@ -170,15 +153,11 @@ class ChapterController extends Controller
 
     /**
      * Perform the move action for a chapter.
-     * @param $bookSlug
-     * @param $chapterSlug
-     * @param Request $request
-     * @return mixed
-     * @throws \BookStack\Exceptions\NotFoundException
+     * @throws NotFoundException
      */
-    public function move($bookSlug, $chapterSlug, Request $request)
+    public function move(Request $request, string $bookSlug, string $chapterSlug)
     {
-        $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $this->checkOwnablePermission('chapter-update', $chapter);
         $this->checkOwnablePermission('chapter-delete', $chapter);
 
@@ -187,100 +166,47 @@ class ChapterController extends Controller
             return redirect($chapter->getUrl());
         }
 
-        $stringExploded = explode(':', $entitySelection);
-        $entityType = $stringExploded[0];
-        $entityId = intval($stringExploded[1]);
-
-        $parent = false;
-
-        if ($entityType == 'book') {
-            $parent = $this->entityRepo->getById('book', $entityId);
-        }
-
-        if ($parent === false || $parent === null) {
-            session()->flash('error', trans('errors.selected_book_not_found'));
+        try {
+            $newBook = $this->chapterRepo->move($chapter, $entitySelection);
+        } catch (MoveOperationException $exception) {
+            $this->showErrorNotification(trans('errors.selected_book_not_found'));
             return redirect()->back();
         }
 
-        $this->entityRepo->changeBook('chapter', $parent->id, $chapter, true);
-        Activity::add($chapter, 'chapter_move', $chapter->book->id);
-        session()->flash('success', trans('entities.chapter_move_success', ['bookName' => $parent->name]));
+        Activity::add($chapter, 'chapter_move', $newBook->id);
 
+        $this->showSuccessNotification(trans('entities.chapter_move_success', ['bookName' => $newBook->name]));
         return redirect($chapter->getUrl());
     }
 
     /**
      * Show the Restrictions view.
-     * @param $bookSlug
-     * @param $chapterSlug
-     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
-     * @throws \BookStack\Exceptions\NotFoundException
+     * @throws NotFoundException
      */
-    public function showPermissions($bookSlug, $chapterSlug)
+    public function showPermissions(string $bookSlug, string $chapterSlug)
     {
-        $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $this->checkOwnablePermission('restrictions-manage', $chapter);
-        $roles = $this->userRepo->getRestrictableRoles();
+
         return view('chapters.permissions', [
             'chapter' => $chapter,
-            'roles' => $roles
         ]);
     }
 
     /**
      * Set the restrictions for this chapter.
-     * @param $bookSlug
-     * @param $chapterSlug
-     * @param Request $request
-     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
-     * @throws \BookStack\Exceptions\NotFoundException
-     * @throws \Throwable
+     * @throws NotFoundException
      */
-    public function permissions($bookSlug, $chapterSlug, Request $request)
+    public function permissions(Request $request, string $bookSlug, string $chapterSlug)
     {
-        $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $this->checkOwnablePermission('restrictions-manage', $chapter);
-        $this->entityRepo->updateEntityPermissionsFromRequest($request, $chapter);
-        session()->flash('success', trans('entities.chapters_permissions_success'));
-        return redirect($chapter->getUrl());
-    }
 
-    /**
-     * Exports a chapter to pdf .
-     * @param string $bookSlug
-     * @param string $chapterSlug
-     * @return \Illuminate\Http\Response
-     */
-    public function exportPdf($bookSlug, $chapterSlug)
-    {
-        $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
-        $pdfContent = $this->exportService->chapterToPdf($chapter);
-        return $this->downloadResponse($pdfContent, $chapterSlug . '.pdf');
-    }
+        $restricted = $request->get('restricted') === 'true';
+        $permissions = $request->filled('restrictions') ? collect($request->get('restrictions')) : null;
+        $this->chapterRepo->updatePermissions($chapter, $restricted, $permissions);
 
-    /**
-     * Export a chapter to a self-contained HTML file.
-     * @param string $bookSlug
-     * @param string $chapterSlug
-     * @return \Illuminate\Http\Response
-     */
-    public function exportHtml($bookSlug, $chapterSlug)
-    {
-        $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
-        $containedHtml = $this->exportService->chapterToContainedHtml($chapter);
-        return $this->downloadResponse($containedHtml, $chapterSlug . '.html');
-    }
-
-    /**
-     * Export a chapter to a simple plaintext .txt file.
-     * @param string $bookSlug
-     * @param string $chapterSlug
-     * @return \Illuminate\Http\Response
-     */
-    public function exportPlainText($bookSlug, $chapterSlug)
-    {
-        $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
-        $chapterText = $this->exportService->chapterToPlainText($chapter);
-        return $this->downloadResponse($chapterText, $chapterSlug . '.txt');
+        $this->showSuccessNotification(trans('entities.chapters_permissions_success'));
+        return redirect($chapter->getUrl());
     }
 }
diff --git a/app/Http/Controllers/ChapterExportController.php b/app/Http/Controllers/ChapterExportController.php
new file mode 100644 (file)
index 0000000..0c86f85
--- /dev/null
@@ -0,0 +1,58 @@
+<?php namespace BookStack\Http\Controllers;
+
+use BookStack\Entities\ExportService;
+use BookStack\Entities\Repos\ChapterRepo;
+use BookStack\Exceptions\NotFoundException;
+use Throwable;
+
+class ChapterExportController extends Controller
+{
+
+    protected $chapterRepo;
+    protected $exportService;
+
+    /**
+     * ChapterExportController constructor.
+     */
+    public function __construct(ChapterRepo $chapterRepo, ExportService $exportService)
+    {
+        $this->chapterRepo = $chapterRepo;
+        $this->exportService = $exportService;
+        parent::__construct();
+    }
+
+    /**
+     * Exports a chapter to pdf.
+     * @throws NotFoundException
+     * @throws Throwable
+     */
+    public function pdf(string $bookSlug, string $chapterSlug)
+    {
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
+        $pdfContent = $this->exportService->chapterToPdf($chapter);
+        return $this->downloadResponse($pdfContent, $chapterSlug . '.pdf');
+    }
+
+    /**
+     * Export a chapter to a self-contained HTML file.
+     * @throws NotFoundException
+     * @throws Throwable
+     */
+    public function html(string $bookSlug, string $chapterSlug)
+    {
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
+        $containedHtml = $this->exportService->chapterToContainedHtml($chapter);
+        return $this->downloadResponse($containedHtml, $chapterSlug . '.html');
+    }
+
+    /**
+     * Export a chapter to a simple plaintext .txt file.
+     * @throws NotFoundException
+     */
+    public function plainText(string $bookSlug, string $chapterSlug)
+    {
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
+        $chapterText = $this->exportService->chapterToPlainText($chapter);
+        return $this->downloadResponse($chapterText, $chapterSlug . '.txt');
+    }
+}
index 860b507623fb6b5d09cd6a2a1f1a4aea5021fc35..068358d72d10af92de45327e0b207093c9c501fd 100644 (file)
@@ -2,44 +2,36 @@
 
 use Activity;
 use BookStack\Actions\CommentRepo;
-use BookStack\Entities\Repos\EntityRepo;
-use Illuminate\Database\Eloquent\ModelNotFoundException;
+use BookStack\Entities\Page;
 use Illuminate\Http\Request;
+use Illuminate\Validation\ValidationException;
 
 class CommentController extends Controller
 {
-    protected $entityRepo;
     protected $commentRepo;
 
     /**
      * CommentController constructor.
-     * @param \BookStack\Entities\Repos\EntityRepo $entityRepo
-     * @param \BookStack\Actions\CommentRepo $commentRepo
      */
-    public function __construct(EntityRepo $entityRepo, CommentRepo $commentRepo)
+    public function __construct(CommentRepo $commentRepo)
     {
-        $this->entityRepo = $entityRepo;
         $this->commentRepo = $commentRepo;
         parent::__construct();
     }
 
     /**
      * Save a new comment for a Page
-     * @param Request $request
-     * @param integer $pageId
-     * @param null|integer $commentId
-     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
+     * @throws ValidationException
      */
-    public function savePageComment(Request $request, $pageId, $commentId = null)
+    public function savePageComment(Request $request, int $pageId, int $commentId = null)
     {
         $this->validate($request, [
             'text' => 'required|string',
             'html' => 'required|string',
         ]);
 
-        try {
-            $page = $this->entityRepo->getById('page', $pageId, true);
-        } catch (ModelNotFoundException $e) {
+        $page = Page::visible()->find($pageId);
+        if ($page === null) {
             return response('Not found', 404);
         }
 
@@ -59,11 +51,9 @@ class CommentController extends Controller
 
     /**
      * Update an existing comment.
-     * @param Request $request
-     * @param integer $commentId
-     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     * @throws ValidationException
      */
-    public function update(Request $request, $commentId)
+    public function update(Request $request, int $commentId)
     {
         $this->validate($request, [
             'text' => 'required|string',
@@ -80,13 +70,12 @@ class CommentController extends Controller
 
     /**
      * Delete a comment from the system.
-     * @param integer $id
-     * @return \Illuminate\Http\JsonResponse
      */
-    public function destroy($id)
+    public function destroy(int $id)
     {
         $comment = $this->commentRepo->getById($id);
         $this->checkOwnablePermission('comment-delete', $comment);
+
         $this->commentRepo->delete($comment);
         return response()->json(['message' => trans('entities.comment_deleted')]);
     }
index 5bc62c601a73bcf63913a2bac3bd63c25dbab2e1..b9576f2febd7923293b8eacb105bab6130f118c1 100644 (file)
@@ -2,7 +2,6 @@
 
 namespace BookStack\Http\Controllers;
 
-use BookStack\Auth\User;
 use BookStack\Ownable;
 use Illuminate\Foundation\Bus\DispatchesJobs;
 use Illuminate\Foundation\Validation\ValidatesRequests;
@@ -14,42 +13,27 @@ abstract class Controller extends BaseController
 {
     use DispatchesJobs, ValidatesRequests;
 
-    /**
-     * @var User static
-     */
-    protected $currentUser;
-    /**
-     * @var bool
-     */
-    protected $signedIn;
-
     /**
      * Controller constructor.
      */
     public function __construct()
     {
-        $this->middleware(function ($request, $next) {
-
-            // Get a user instance for the current user
-            $user = user();
-
-            // Share variables with controllers
-            $this->currentUser = $user;
-            $this->signedIn = auth()->check();
-
-            // Share variables with views
-            view()->share('signedIn', $this->signedIn);
-            view()->share('currentUser', $user);
+        //
+    }
 
-            return $next($request);
-        });
+    /**
+     * Check if the current user is signed in.
+     */
+    protected function isSignedIn(): bool
+    {
+        return auth()->check();
     }
 
     /**
      * Stops the application and shows a permission error if
      * the application is in demo mode.
      */
-    protected function preventAccessForDemoUsers()
+    protected function preventAccessInDemoMode()
     {
         if (config('app.env') === 'demo') {
             $this->showPermissionError();
@@ -75,7 +59,7 @@ abstract class Controller extends BaseController
             $response = response()->json(['error' => trans('errors.permissionJson')], 403);
         } else {
             $response = redirect('/');
-            session()->flash('error', trans('errors.permission'));
+            $this->showErrorNotification(trans('errors.permission'));
         }
 
         throw new HttpResponseException($response);
@@ -133,7 +117,7 @@ abstract class Controller extends BaseController
     protected function checkPermissionOrCurrentUser(string $permissionName, int $userId)
     {
         return $this->checkPermissionOr($permissionName, function () use ($userId) {
-            return $userId === $this->currentUser->id;
+            return $userId === user()->id;
         });
     }
 
@@ -145,7 +129,7 @@ abstract class Controller extends BaseController
      */
     protected function jsonError($messageText = "", $statusCode = 500)
     {
-        return response()->json(['message' => $messageText], $statusCode);
+        return response()->json(['message' => $messageText, 'status' => 'error'], $statusCode);
     }
 
     /**
@@ -178,4 +162,39 @@ abstract class Controller extends BaseController
             'Content-Disposition' => 'attachment; filename="' . $fileName . '"'
         ]);
     }
+
+    /**
+     * Show a positive, successful notification to the user on next view load.
+     * @param string $message
+     */
+    protected function showSuccessNotification(string $message)
+    {
+        session()->flash('success', $message);
+    }
+
+    /**
+     * Show a warning notification to the user on next view load.
+     * @param string $message
+     */
+    protected function showWarningNotification(string $message)
+    {
+        session()->flash('warning', $message);
+    }
+
+    /**
+     * Show an error notification to the user on next view load.
+     * @param string $message
+     */
+    protected function showErrorNotification(string $message)
+    {
+        session()->flash('error', $message);
+    }
+
+    /**
+     * Get the validation rules for image files.
+     */
+    protected function getImageValidationRules(): string
+    {
+        return 'image_extension|no_double_extension|mimes:jpeg,png,gif,bmp,webp,tiff';
+    }
 }
index d2c75f95622fee3d8278629dd75b3390268dcb38..260952fd16eb8e18962bc962ac3db1acc3f13445 100644 (file)
@@ -1,23 +1,16 @@
 <?php namespace BookStack\Http\Controllers;
 
 use Activity;
-use BookStack\Entities\Repos\EntityRepo;
+use BookStack\Entities\Book;
+use BookStack\Entities\Managers\PageContent;
+use BookStack\Entities\Page;
+use BookStack\Entities\Repos\BookRepo;
+use BookStack\Entities\Repos\BookshelfRepo;
 use Illuminate\Http\Response;
 use Views;
 
 class HomeController extends Controller
 {
-    protected $entityRepo;
-
-    /**
-     * HomeController constructor.
-     * @param EntityRepo $entityRepo
-     */
-    public function __construct(EntityRepo $entityRepo)
-    {
-        $this->entityRepo = $entityRepo;
-        parent::__construct();
-    }
 
     /**
      * Display the homepage.
@@ -26,10 +19,20 @@ class HomeController extends Controller
     public function index()
     {
         $activity = Activity::latest(10);
-        $draftPages = $this->signedIn ? $this->entityRepo->getUserDraftPages(6) : [];
+        $draftPages = [];
+
+        if ($this->isSignedIn()) {
+            $draftPages = Page::visible()->where('draft', '=', true)
+                ->where('created_by', '=', user()->id)
+                ->orderBy('updated_at', 'desc')->take(6)->get();
+        }
+
         $recentFactor = count($draftPages) > 0 ? 0.5 : 1;
-        $recents = $this->signedIn ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreated('book', 12*$recentFactor);
-        $recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdated('page', 12);
+        $recents = $this->isSignedIn() ?
+              Views::getUserRecentlyViewed(12*$recentFactor, 0)
+            : Book::visible()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get();
+        $recentlyUpdatedPages = Page::visible()->where('draft', false)
+            ->orderBy('updated_at', 'desc')->take(12)->get();
 
         $homepageOptions = ['default', 'books', 'bookshelves', 'page'];
         $homepageOption = setting('app-homepage-type', 'default');
@@ -47,9 +50,9 @@ class HomeController extends Controller
         // Add required list ordering & sorting for books & shelves views.
         if ($homepageOption === 'bookshelves' || $homepageOption === 'books') {
             $key = $homepageOption;
-            $view = setting()->getUser($this->currentUser, $key . '_view_type', config('app.views.' . $key));
-            $sort = setting()->getUser($this->currentUser, $key . '_sort', 'name');
-            $order = setting()->getUser($this->currentUser, $key . '_sort_order', 'asc');
+            $view = setting()->getForCurrentUser($key . '_view_type', config('app.views.' . $key));
+            $sort = setting()->getForCurrentUser($key . '_sort', 'name');
+            $order = setting()->getForCurrentUser($key . '_sort_order', 'asc');
 
             $sortOptions = [
                 'name' => trans('common.sort_name'),
@@ -66,16 +69,18 @@ class HomeController extends Controller
         }
 
         if ($homepageOption === 'bookshelves') {
-            $shelves = $this->entityRepo->getAllPaginated('bookshelf', 18, $commonData['sort'], $commonData['order']);
+            $shelfRepo = app(BookshelfRepo::class);
+            $shelves = app(BookshelfRepo::class)->getAllPaginated(18, $commonData['sort'], $commonData['order']);
             foreach ($shelves as $shelf) {
-                $shelf->books = $this->entityRepo->getBookshelfChildren($shelf);
+                $shelf->books = $shelf->visibleBooks;
             }
             $data = array_merge($commonData, ['shelves' => $shelves]);
             return view('common.home-shelves', $data);
         }
 
         if ($homepageOption === 'books') {
-            $books = $this->entityRepo->getAllPaginated('book', 18, $commonData['sort'], $commonData['order']);
+            $bookRepo = app(BookRepo::class);
+            $books = $bookRepo->getAllPaginated(18, $commonData['sort'], $commonData['order']);
             $data = array_merge($commonData, ['books' => $books]);
             return view('common.home-book', $data);
         }
@@ -83,8 +88,9 @@ class HomeController extends Controller
         if ($homepageOption === 'page') {
             $homepageSetting = setting('app-homepage', '0:');
             $id = intval(explode(':', $homepageSetting)[0]);
-            $customHomepage = $this->entityRepo->getById('page', $id, false, true);
-            $this->entityRepo->renderPage($customHomepage, true);
+            $customHomepage = Page::query()->where('draft', '=', false)->findOrFail($id);
+            $pageContent = new PageContent($customHomepage);
+            $customHomepage->html = $pageContent->render(true);
             return view('common.home-custom', array_merge($commonData, ['customHomepage' => $customHomepage]));
         }
 
index 024003f87d13c4a40611b7942ce83d3cbdcf9a43..9c67704dd339bd0b21d3471b751206625386869b 100644 (file)
@@ -1,6 +1,6 @@
 <?php namespace BookStack\Http\Controllers\Images;
 
-use BookStack\Entities\Repos\EntityRepo;
+use BookStack\Entities\Page;
 use BookStack\Exceptions\ImageUploadException;
 use BookStack\Http\Controllers\Controller;
 use BookStack\Repos\PageRepo;
@@ -47,13 +47,13 @@ class ImageController extends Controller
 
     /**
      * Update image details
-     * @param integer $id
      * @param Request $request
+     * @param integer $id
      * @return \Illuminate\Http\JsonResponse
      * @throws ImageUploadException
      * @throws \Exception
      */
-    public function update($id, Request $request)
+    public function update(Request $request, $id)
     {
         $this->validate($request, [
             'name' => 'required|min:2|string'
@@ -69,16 +69,21 @@ class ImageController extends Controller
 
     /**
      * Show the usage of an image on pages.
-     * @param \BookStack\Entities\Repos\EntityRepo $entityRepo
-     * @param $id
-     * @return \Illuminate\Http\JsonResponse
      */
-    public function usage(EntityRepo $entityRepo, $id)
+    public function usage(int $id)
     {
         $image = $this->imageRepo->getById($id);
         $this->checkImagePermission($image);
-        $pageSearch = $entityRepo->searchForImage($image->url);
-        return response()->json($pageSearch);
+
+        $pages = Page::visible()->where('html', 'like', '%' . $image->url . '%')->get(['id', 'name', 'slug', 'book_id']);
+        foreach ($pages as $page) {
+            $page->url = $page->getUrl();
+            $page->html = '';
+            $page->text = '';
+        }
+        $result = count($pages) > 0 ? $pages : false;
+
+        return response()->json($result);
     }
 
     /**
index 89fb83fd97f116b2005827b30cce0ff459d81e5b..630f888ed3e9dc0d16da96b622a03278b527cfb9 100644 (file)
@@ -1,61 +1,46 @@
 <?php namespace BookStack\Http\Controllers;
 
 use Activity;
-use BookStack\Auth\UserRepo;
-use BookStack\Entities\Repos\EntityRepo;
-use BookStack\Entities\ExportService;
+use BookStack\Entities\Managers\BookContents;
+use BookStack\Entities\Managers\PageContent;
+use BookStack\Entities\Managers\PageEditActivity;
+use BookStack\Entities\Page;
 use BookStack\Entities\Repos\PageRepo;
 use BookStack\Exceptions\NotFoundException;
-use GatherContent\Htmldiff\Htmldiff;
+use BookStack\Exceptions\NotifyException;
+use BookStack\Exceptions\PermissionsException;
+use Exception;
 use Illuminate\Http\Request;
-use Illuminate\Http\Response;
+use Illuminate\Validation\ValidationException;
+use Throwable;
 use Views;
 
 class PageController extends Controller
 {
 
     protected $pageRepo;
-    protected $exportService;
-    protected $userRepo;
 
     /**
      * PageController constructor.
-     * @param \BookStack\Entities\Repos\PageRepo $pageRepo
-     * @param \BookStack\Entities\ExportService $exportService
-     * @param UserRepo $userRepo
      */
-    public function __construct(PageRepo $pageRepo, ExportService $exportService, UserRepo $userRepo)
+    public function __construct(PageRepo $pageRepo)
     {
         $this->pageRepo = $pageRepo;
-        $this->exportService = $exportService;
-        $this->userRepo = $userRepo;
         parent::__construct();
     }
 
     /**
      * Show the form for creating a new page.
-     * @param string $bookSlug
-     * @param string $chapterSlug
-     * @return Response
-     * @internal param bool $pageSlug
-     * @throws NotFoundException
+     * @throws Throwable
      */
-    public function create($bookSlug, $chapterSlug = null)
+    public function create(string $bookSlug, string $chapterSlug = null)
     {
-        if ($chapterSlug !== null) {
-            $chapter = $this->pageRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
-            $book = $chapter->book;
-        } else {
-            $chapter = null;
-            $book = $this->pageRepo->getBySlug('book', $bookSlug);
-        }
-
-        $parent = $chapter ? $chapter : $book;
+        $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug);
         $this->checkOwnablePermission('page-create', $parent);
 
         // Redirect to draft edit screen if signed in
-        if ($this->signedIn) {
-            $draft = $this->pageRepo->getDraftPage($book, $chapter);
+        if ($this->isSignedIn()) {
+            $draft = $this->pageRepo->getNewDraftPage($parent);
             return redirect($draft->getUrl());
         }
 
@@ -66,117 +51,94 @@ class PageController extends Controller
 
     /**
      * Create a new page as a guest user.
-     * @param Request $request
-     * @param string $bookSlug
-     * @param string|null $chapterSlug
-     * @return mixed
-     * @throws NotFoundException
+     * @throws ValidationException
      */
-    public function createAsGuest(Request $request, $bookSlug, $chapterSlug = null)
+    public function createAsGuest(Request $request, string $bookSlug, string $chapterSlug = null)
     {
         $this->validate($request, [
             'name' => 'required|string|max:255'
         ]);
 
-        if ($chapterSlug !== null) {
-            $chapter = $this->pageRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
-            $book = $chapter->book;
-        } else {
-            $chapter = null;
-            $book = $this->pageRepo->getBySlug('book', $bookSlug);
-        }
-
-        $parent = $chapter ? $chapter : $book;
+        $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug);
         $this->checkOwnablePermission('page-create', $parent);
 
-        $page = $this->pageRepo->getDraftPage($book, $chapter);
-        $this->pageRepo->publishPageDraft($page, [
+        $page = $this->pageRepo->getNewDraftPage($parent);
+        $this->pageRepo->publishDraft($page, [
             'name' => $request->get('name'),
             'html' => ''
         ]);
+
         return redirect($page->getUrl('/edit'));
     }
 
     /**
      * Show form to continue editing a draft page.
-     * @param string $bookSlug
-     * @param int $pageId
-     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     * @throws NotFoundException
      */
-    public function editDraft($bookSlug, $pageId)
+    public function editDraft(string $bookSlug, int $pageId)
     {
-        $draft = $this->pageRepo->getById('page', $pageId, true);
-        $this->checkOwnablePermission('page-create', $draft->parent);
+        $draft = $this->pageRepo->getById($pageId);
+        $this->checkOwnablePermission('page-create', $draft->parent());
         $this->setPageTitle(trans('entities.pages_edit_draft'));
 
-        $draftsEnabled = $this->signedIn;
+        $draftsEnabled = $this->isSignedIn();
+        $templates = $this->pageRepo->getTemplates(10);
+
         return view('pages.edit', [
             'page' => $draft,
             'book' => $draft->book,
             'isDraft' => true,
-            'draftsEnabled' => $draftsEnabled
+            'draftsEnabled' => $draftsEnabled,
+            'templates' => $templates,
         ]);
     }
 
     /**
      * Store a new page by changing a draft into a page.
-     * @param  Request $request
-     * @param  string $bookSlug
-     * @param  int $pageId
-     * @return Response
+     * @throws NotFoundException
+     * @throws ValidationException
      */
-    public function store(Request $request, $bookSlug, $pageId)
+    public function store(Request $request, string $bookSlug, int $pageId)
     {
         $this->validate($request, [
             'name' => 'required|string|max:255'
         ]);
+        $draftPage = $this->pageRepo->getById($pageId);
+        $this->checkOwnablePermission('page-create', $draftPage->parent());
 
-        $input = $request->all();
-        $draftPage = $this->pageRepo->getById('page', $pageId, true);
-        $book = $draftPage->book;
-
-        $parent = $draftPage->parent;
-        $this->checkOwnablePermission('page-create', $parent);
-
-        if ($parent->isA('chapter')) {
-            $input['priority'] = $this->pageRepo->getNewChapterPriority($parent);
-        } else {
-            $input['priority'] = $this->pageRepo->getNewBookPriority($parent);
-        }
-
-        $page = $this->pageRepo->publishPageDraft($draftPage, $input);
+        $page = $this->pageRepo->publishDraft($draftPage, $request->all());
+        Activity::add($page, 'page_create', $draftPage->book->id);
 
-        Activity::add($page, 'page_create', $book->id);
         return redirect($page->getUrl());
     }
 
     /**
      * Display the specified page.
      * If the page is not found via the slug the revisions are searched for a match.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @return Response
      * @throws NotFoundException
      */
-    public function show($bookSlug, $pageSlug)
+    public function show(string $bookSlug, string $pageSlug)
     {
         try {
-            $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
+            $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         } catch (NotFoundException $e) {
-            $page = $this->pageRepo->getPageByOldSlug($pageSlug, $bookSlug);
+            $page = $this->pageRepo->getByOldSlug($bookSlug, $pageSlug);
+
             if ($page === null) {
                 throw $e;
             }
+
             return redirect($page->getUrl());
         }
 
         $this->checkOwnablePermission('page-view', $page);
 
-        $page->html = $this->pageRepo->renderPage($page);
-        $sidebarTree = $this->pageRepo->getBookChildren($page->book);
-        $pageNav = $this->pageRepo->getPageNav($page->html);
+        $pageContent = (new PageContent($page));
+        $page->html = $pageContent->render();
+        $sidebarTree = (new BookContents($page->book))->getTree();
+        $pageNav = $pageContent->getNavigation($page->html);
 
-        // check if the comment's are enabled
+        // Check if page comments are enabled
         $commentsEnabled = !setting('app-disable-comments');
         if ($commentsEnabled) {
             $page->load(['comments.createdBy']);
@@ -185,7 +147,8 @@ class PageController extends Controller
         Views::add($page);
         $this->setPageTitle($page->getShortName());
         return view('pages.show', [
-            'page' => $page,'book' => $page->book,
+            'page' => $page,
+            'book' => $page->book,
             'current' => $page,
             'sidebarTree' => $sidebarTree,
             'commentsEnabled' => $commentsEnabled,
@@ -195,93 +158,86 @@ class PageController extends Controller
 
     /**
      * Get page from an ajax request.
-     * @param int $pageId
-     * @return \Illuminate\Http\JsonResponse
+     * @throws NotFoundException
      */
-    public function getPageAjax($pageId)
+    public function getPageAjax(int $pageId)
     {
-        $page = $this->pageRepo->getById('page', $pageId);
+        $page = $this->pageRepo->getById($pageId);
         return response()->json($page);
     }
 
     /**
      * Show the form for editing the specified page.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @return Response
      * @throws NotFoundException
      */
-    public function edit($bookSlug, $pageSlug)
+    public function edit(string $bookSlug, string $pageSlug)
     {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('page-update', $page);
-        $this->setPageTitle(trans('entities.pages_editing_named', ['pageName'=>$page->getShortName()]));
+
         $page->isDraft = false;
+        $editActivity = new PageEditActivity($page);
 
         // Check for active editing
         $warnings = [];
-        if ($this->pageRepo->isPageEditingActive($page, 60)) {
-            $warnings[] = $this->pageRepo->getPageEditingActiveMessage($page, 60);
+        if ($editActivity->hasActiveEditing()) {
+            $warnings[] = $editActivity->activeEditingMessage();
         }
 
         // Check for a current draft version for this user
-        $userPageDraft = $this->pageRepo->getUserPageDraft($page, $this->currentUser->id);
-        if ($userPageDraft !== null) {
-            $page->name = $userPageDraft->name;
-            $page->html = $userPageDraft->html;
-            $page->markdown = $userPageDraft->markdown;
+        $userDraft = $this->pageRepo->getUserDraft($page);
+        if ($userDraft !== null) {
+            $page->forceFill($userDraft->only(['name', 'html', 'markdown']));
             $page->isDraft = true;
-            $warnings [] = $this->pageRepo->getUserPageDraftMessage($userPageDraft);
+            $warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft);
         }
 
         if (count($warnings) > 0) {
-            session()->flash('warning', implode("\n", $warnings));
+            $this->showWarningNotification(implode("\n", $warnings));
         }
 
-        $draftsEnabled = $this->signedIn;
+        $templates = $this->pageRepo->getTemplates(10);
+        $draftsEnabled = $this->isSignedIn();
+        $this->setPageTitle(trans('entities.pages_editing_named', ['pageName' => $page->getShortName()]));
         return view('pages.edit', [
             'page' => $page,
             'book' => $page->book,
             'current' => $page,
-            'draftsEnabled' => $draftsEnabled
+            'draftsEnabled' => $draftsEnabled,
+            'templates' => $templates,
         ]);
     }
 
     /**
      * Update the specified page in storage.
-     * @param  Request $request
-     * @param  string $bookSlug
-     * @param  string $pageSlug
-     * @return Response
+     * @throws ValidationException
+     * @throws NotFoundException
      */
-    public function update(Request $request, $bookSlug, $pageSlug)
+    public function update(Request $request, string $bookSlug, string $pageSlug)
     {
         $this->validate($request, [
             'name' => 'required|string|max:255'
         ]);
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('page-update', $page);
-        $this->pageRepo->updatePage($page, $page->book->id, $request->all());
+
+        $this->pageRepo->update($page, $request->all());
         Activity::add($page, 'page_update', $page->book->id);
+
         return redirect($page->getUrl());
     }
 
     /**
      * Save a draft update as a revision.
-     * @param Request $request
-     * @param int $pageId
-     * @return \Illuminate\Http\JsonResponse
+     * @throws NotFoundException
      */
-    public function saveDraft(Request $request, $pageId)
+    public function saveDraft(Request $request, int $pageId)
     {
-        $page = $this->pageRepo->getById('page', $pageId, true);
+        $page = $this->pageRepo->getById($pageId);
         $this->checkOwnablePermission('page-update', $page);
 
-        if (!$this->signedIn) {
-            return response()->json([
-                'status' => 'error',
-                'message' => trans('errors.guests_cannot_save_drafts'),
-            ], 500);
+        if (!$this->isSignedIn()) {
+            return $this->jsonError(trans('errors.guests_cannot_save_drafts'), 500);
         }
 
         $draft = $this->pageRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
@@ -295,253 +251,98 @@ class PageController extends Controller
     }
 
     /**
-     * Redirect from a special link url which
-     * uses the page id rather than the name.
-     * @param int $pageId
-     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+     * Redirect from a special link url which uses the page id rather than the name.
+     * @throws NotFoundException
      */
-    public function redirectFromLink($pageId)
+    public function redirectFromLink(int $pageId)
     {
-        $page = $this->pageRepo->getById('page', $pageId);
+        $page = $this->pageRepo->getById($pageId);
         return redirect($page->getUrl());
     }
 
     /**
      * Show the deletion page for the specified page.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @return \Illuminate\View\View
+     * @throws NotFoundException
      */
-    public function showDelete($bookSlug, $pageSlug)
+    public function showDelete(string $bookSlug, string $pageSlug)
     {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('page-delete', $page);
         $this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()]));
-        return view('pages.delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
+        return view('pages.delete', [
+            'book' => $page->book,
+            'page' => $page,
+            'current' => $page
+        ]);
     }
 
-
     /**
      * Show the deletion page for the specified page.
-     * @param string $bookSlug
-     * @param int $pageId
-     * @return \Illuminate\View\View
      * @throws NotFoundException
      */
-    public function showDeleteDraft($bookSlug, $pageId)
+    public function showDeleteDraft(string $bookSlug, int $pageId)
     {
-        $page = $this->pageRepo->getById('page', $pageId, true);
+        $page = $this->pageRepo->getById($pageId);
         $this->checkOwnablePermission('page-update', $page);
         $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName'=>$page->getShortName()]));
-        return view('pages.delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
+        return view('pages.delete', [
+            'book' => $page->book,
+            'page' => $page,
+            'current' => $page
+        ]);
     }
 
     /**
      * Remove the specified page from storage.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @return Response
-     * @internal param int $id
+     * @throws NotFoundException
+     * @throws Throwable
+     * @throws NotifyException
      */
-    public function destroy($bookSlug, $pageSlug)
+    public function destroy(string $bookSlug, string $pageSlug)
     {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
-        $book = $page->book;
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('page-delete', $page);
-        $this->pageRepo->destroyPage($page);
 
-        Activity::addMessage('page_delete', $book->id, $page->name);
-        session()->flash('success', trans('entities.pages_delete_success'));
+        $book = $page->book;
+        $this->pageRepo->destroy($page);
+        Activity::addMessage('page_delete', $page->name, $book->id);
+
+        $this->showSuccessNotification(trans('entities.pages_delete_success'));
         return redirect($book->getUrl());
     }
 
     /**
      * Remove the specified draft page from storage.
-     * @param string $bookSlug
-     * @param int $pageId
-     * @return Response
      * @throws NotFoundException
+     * @throws NotifyException
+     * @throws Throwable
      */
-    public function destroyDraft($bookSlug, $pageId)
+    public function destroyDraft(string $bookSlug, int $pageId)
     {
-        $page = $this->pageRepo->getById('page', $pageId, true);
+        $page = $this->pageRepo->getById($pageId);
         $book = $page->book;
+        $chapter = $page->chapter;
         $this->checkOwnablePermission('page-update', $page);
-        session()->flash('success', trans('entities.pages_delete_draft_success'));
-        $this->pageRepo->destroyPage($page);
-        return redirect($book->getUrl());
-    }
-
-    /**
-     * Shows the last revisions for this page.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @return \Illuminate\View\View
-     * @throws NotFoundException
-     */
-    public function showRevisions($bookSlug, $pageSlug)
-    {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
-        $this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()]));
-        return view('pages.revisions', ['page' => $page, 'current' => $page]);
-    }
 
-    /**
-     * Shows a preview of a single revision
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @param int $revisionId
-     * @return \Illuminate\View\View
-     */
-    public function showRevision($bookSlug, $pageSlug, $revisionId)
-    {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
-        $revision = $page->revisions()->where('id', '=', $revisionId)->first();
-        if ($revision === null) {
-            abort(404);
-        }
+        $this->pageRepo->destroy($page);
 
-        $page->fill($revision->toArray());
-        $this->setPageTitle(trans('entities.pages_revision_named', ['pageName' => $page->getShortName()]));
+        $this->showSuccessNotification(trans('entities.pages_delete_draft_success'));
 
-        return view('pages.revision', [
-            'page' => $page,
-            'book' => $page->book,
-            'diff' => null,
-            'revision' => $revision
-        ]);
-    }
-
-    /**
-     * Shows the changes of a single revision
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @param int $revisionId
-     * @return \Illuminate\View\View
-     */
-    public function showRevisionChanges($bookSlug, $pageSlug, $revisionId)
-    {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
-        $revision = $page->revisions()->where('id', '=', $revisionId)->first();
-        if ($revision === null) {
-            abort(404);
+        if ($chapter && userCan('view', $chapter)) {
+            return redirect($chapter->getUrl());
         }
-
-        $prev = $revision->getPrevious();
-        $prevContent = ($prev === null) ? '' : $prev->html;
-        $diff = (new Htmldiff)->diff($prevContent, $revision->html);
-
-        $page->fill($revision->toArray());
-        $this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()]));
-
-        return view('pages.revision', [
-            'page' => $page,
-            'book' => $page->book,
-            'diff' => $diff,
-            'revision' => $revision
-        ]);
-    }
-
-    /**
-     * Restores a page using the content of the specified revision.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @param int $revisionId
-     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
-     */
-    public function restoreRevision($bookSlug, $pageSlug, $revisionId)
-    {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
-        $this->checkOwnablePermission('page-update', $page);
-        $page = $this->pageRepo->restorePageRevision($page, $page->book, $revisionId);
-        Activity::add($page, 'page_restore', $page->book->id);
-        return redirect($page->getUrl());
-    }
-
-
-    /**
-     * Deletes a revision using the id of the specified revision.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @param int $revId
-     * @throws NotFoundException
-     * @throws BadRequestException
-     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
-     */
-    public function destroyRevision($bookSlug, $pageSlug, $revId)
-    {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
-        $this->checkOwnablePermission('page-delete', $page);
-
-        $revision = $page->revisions()->where('id', '=', $revId)->first();
-        if ($revision === null) {
-            throw new NotFoundException("Revision #{$revId} not found");
-        }
-
-        // Get the current revision for the page
-        $currentRevision = $page->getCurrentRevision();
-
-        // Check if its the latest revision, cannot delete latest revision.
-        if (intval($currentRevision->id) === intval($revId)) {
-            session()->flash('error', trans('entities.revision_cannot_delete_latest'));
-            return response()->view('pages.revisions', ['page' => $page, 'book' => $page->book, 'current' => $page], 400);
-        }
-
-        $revision->delete();
-        session()->flash('success', trans('entities.revision_delete_success'));
-        return view('pages.revisions', ['page' => $page, 'book' => $page->book, 'current' => $page]);
-    }
-
-    /**
-     * Exports a page to a PDF.
-     * https://github.com/barryvdh/laravel-dompdf
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @return \Illuminate\Http\Response
-     */
-    public function exportPdf($bookSlug, $pageSlug)
-    {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
-        $page->html = $this->pageRepo->renderPage($page);
-        $pdfContent = $this->exportService->pageToPdf($page);
-        return $this->downloadResponse($pdfContent, $pageSlug . '.pdf');
-    }
-
-    /**
-     * Export a page to a self-contained HTML file.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @return \Illuminate\Http\Response
-     */
-    public function exportHtml($bookSlug, $pageSlug)
-    {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
-        $page->html = $this->pageRepo->renderPage($page);
-        $containedHtml = $this->exportService->pageToContainedHtml($page);
-        return $this->downloadResponse($containedHtml, $pageSlug . '.html');
-    }
-
-    /**
-     * Export a page to a simple plaintext .txt file.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @return \Illuminate\Http\Response
-     */
-    public function exportPlainText($bookSlug, $pageSlug)
-    {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
-        $pageText = $this->exportService->pageToPlainText($page);
-        return $this->downloadResponse($pageText, $pageSlug . '.txt');
+        return redirect($book->getUrl());
     }
 
     /**
-     * Show a listing of recently created pages
-     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     * Show a listing of recently created pages.
      */
     public function showRecentlyUpdated()
     {
-        // TODO - Still exist?
-        $pages = $this->pageRepo->getRecentlyUpdatedPaginated('page', 20)->setPath(url('/pages/recently-updated'));
+        $pages = Page::visible()->orderBy('updated_at', 'desc')
+            ->paginate(20)
+            ->setPath(url('/pages/recently-updated'));
+
         return view('pages.detailed-listing', [
             'title' => trans('entities.recently_updated_pages'),
             'pages' => $pages
@@ -550,14 +351,11 @@ class PageController extends Controller
 
     /**
      * Show the view to choose a new parent to move a page into.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @return mixed
      * @throws NotFoundException
      */
-    public function showMove($bookSlug, $pageSlug)
+    public function showMove(string $bookSlug, string $pageSlug)
     {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('page-update', $page);
         $this->checkOwnablePermission('page-delete', $page);
         return view('pages.move', [
@@ -567,16 +365,13 @@ class PageController extends Controller
     }
 
     /**
-     * Does the action of moving the location of a page
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @param Request $request
-     * @return mixed
+     * Does the action of moving the location of a page.
      * @throws NotFoundException
+     * @throws Throwable
      */
-    public function move($bookSlug, $pageSlug, Request $request)
+    public function move(Request $request, string $bookSlug, string $pageSlug)
     {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('page-update', $page);
         $this->checkOwnablePermission('page-delete', $page);
 
@@ -585,37 +380,29 @@ class PageController extends Controller
             return redirect($page->getUrl());
         }
 
-        $stringExploded = explode(':', $entitySelection);
-        $entityType = $stringExploded[0];
-        $entityId = intval($stringExploded[1]);
-
-
         try {
-            $parent = $this->pageRepo->getById($entityType, $entityId);
-        } catch (\Exception $e) {
-            session()->flash(trans('entities.selected_book_chapter_not_found'));
+            $parent = $this->pageRepo->move($page, $entitySelection);
+        } catch (Exception $exception) {
+            if ($exception instanceof  PermissionsException) {
+                $this->showPermissionError();
+            }
+
+            $this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
             return redirect()->back();
         }
 
-        $this->checkOwnablePermission('page-create', $parent);
-
-        $this->pageRepo->changePageParent($page, $parent);
         Activity::add($page, 'page_move', $page->book->id);
-        session()->flash('success', trans('entities.pages_move_success', ['parentName' => $parent->name]));
-
+        $this->showSuccessNotification(trans('entities.pages_move_success', ['parentName' => $parent->name]));
         return redirect($page->getUrl());
     }
 
     /**
      * Show the view to copy a page.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @return mixed
      * @throws NotFoundException
      */
-    public function showCopy($bookSlug, $pageSlug)
+    public function showCopy(string $bookSlug, string $pageSlug)
     {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('page-view', $page);
         session()->flashInput(['name' => $page->name]);
         return view('pages.copy', [
@@ -624,78 +411,65 @@ class PageController extends Controller
         ]);
     }
 
+
     /**
      * Create a copy of a page within the requested target destination.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @param Request $request
-     * @return mixed
      * @throws NotFoundException
+     * @throws Throwable
      */
-    public function copy($bookSlug, $pageSlug, Request $request)
+    public function copy(Request $request, string $bookSlug, string $pageSlug)
     {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('page-view', $page);
 
-        $entitySelection = $request->get('entity_selection', null);
-        if ($entitySelection === null || $entitySelection === '') {
-            $parent = $page->chapter ? $page->chapter : $page->book;
-        } else {
-            $stringExploded = explode(':', $entitySelection);
-            $entityType = $stringExploded[0];
-            $entityId = intval($stringExploded[1]);
-
-            try {
-                $parent = $this->pageRepo->getById($entityType, $entityId);
-            } catch (\Exception $e) {
-                session()->flash(trans('entities.selected_book_chapter_not_found'));
-                return redirect()->back();
-            }
-        }
+        $entitySelection = $request->get('entity_selection', null) ?? null;
+        $newName = $request->get('name', null);
 
-        $this->checkOwnablePermission('page-create', $parent);
+        try {
+            $pageCopy = $this->pageRepo->copy($page, $entitySelection, $newName);
+        } catch (Exception $exception) {
+            if ($exception instanceof  PermissionsException) {
+                $this->showPermissionError();
+            }
 
-        $pageCopy = $this->pageRepo->copyPage($page, $parent, $request->get('name', ''));
+            $this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
+            return redirect()->back();
+        }
 
         Activity::add($pageCopy, 'page_create', $pageCopy->book->id);
-        session()->flash('success', trans('entities.pages_copy_success'));
 
+        $this->showSuccessNotification(trans('entities.pages_copy_success'));
         return redirect($pageCopy->getUrl());
     }
 
     /**
      * Show the Permissions view.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
      * @throws NotFoundException
      */
-    public function showPermissions($bookSlug, $pageSlug)
+    public function showPermissions(string $bookSlug, string $pageSlug)
     {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('restrictions-manage', $page);
-        $roles = $this->userRepo->getRestrictableRoles();
         return view('pages.permissions', [
             'page'  => $page,
-            'roles' => $roles
         ]);
     }
 
     /**
      * Set the permissions for this page.
-     * @param string $bookSlug
-     * @param string $pageSlug
-     * @param Request $request
-     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
      * @throws NotFoundException
-     * @throws \Throwable
+     * @throws Throwable
      */
-    public function permissions($bookSlug, $pageSlug, Request $request)
+    public function permissions(Request $request, string $bookSlug, string $pageSlug)
     {
-        $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('restrictions-manage', $page);
-        $this->pageRepo->updateEntityPermissionsFromRequest($request, $page);
-        session()->flash('success', trans('entities.pages_permissions_success'));
+
+        $restricted = $request->get('restricted') === 'true';
+        $permissions = $request->filled('restrictions') ? collect($request->get('restrictions')) : null;
+        $this->pageRepo->updatePermissions($page, $restricted, $permissions);
+
+        $this->showSuccessNotification(trans('entities.pages_permissions_success'));
         return redirect($page->getUrl());
     }
 }
diff --git a/app/Http/Controllers/PageExportController.php b/app/Http/Controllers/PageExportController.php
new file mode 100644 (file)
index 0000000..3b02ea2
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+namespace BookStack\Http\Controllers;
+
+use BookStack\Entities\ExportService;
+use BookStack\Entities\Managers\PageContent;
+use BookStack\Entities\Repos\PageRepo;
+use BookStack\Exceptions\NotFoundException;
+use Throwable;
+
+class PageExportController extends Controller
+{
+
+    protected $pageRepo;
+    protected $exportService;
+
+    /**
+     * PageExportController constructor.
+     * @param PageRepo $pageRepo
+     * @param ExportService $exportService
+     */
+    public function __construct(PageRepo $pageRepo, ExportService $exportService)
+    {
+        $this->pageRepo = $pageRepo;
+        $this->exportService = $exportService;
+        parent::__construct();
+    }
+
+    /**
+     * Exports a page to a PDF.
+     * https://github.com/barryvdh/laravel-dompdf
+     * @throws NotFoundException
+     * @throws Throwable
+     */
+    public function pdf(string $bookSlug, string $pageSlug)
+    {
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
+        $page->html = (new PageContent($page))->render();
+        $pdfContent = $this->exportService->pageToPdf($page);
+        return $this->downloadResponse($pdfContent, $pageSlug . '.pdf');
+    }
+
+    /**
+     * Export a page to a self-contained HTML file.
+     * @throws NotFoundException
+     * @throws Throwable
+     */
+    public function html(string $bookSlug, string $pageSlug)
+    {
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
+        $page->html = (new PageContent($page))->render();
+        $containedHtml = $this->exportService->pageToContainedHtml($page);
+        return $this->downloadResponse($containedHtml, $pageSlug . '.html');
+    }
+
+    /**
+     * Export a page to a simple plaintext .txt file.
+     * @throws NotFoundException
+     */
+    public function plainText(string $bookSlug, string $pageSlug)
+    {
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
+        $pageText = $this->exportService->pageToPlainText($page);
+        return $this->downloadResponse($pageText, $pageSlug . '.txt');
+    }
+}
diff --git a/app/Http/Controllers/PageRevisionController.php b/app/Http/Controllers/PageRevisionController.php
new file mode 100644 (file)
index 0000000..3c65b50
--- /dev/null
@@ -0,0 +1,128 @@
+<?php namespace BookStack\Http\Controllers;
+
+use BookStack\Entities\Repos\PageRepo;
+use BookStack\Exceptions\NotFoundException;
+use BookStack\Facades\Activity;
+use GatherContent\Htmldiff\Htmldiff;
+
+class PageRevisionController extends Controller
+{
+
+    protected $pageRepo;
+
+    /**
+     * PageRevisionController constructor.
+     */
+    public function __construct(PageRepo $pageRepo)
+    {
+        $this->pageRepo = $pageRepo;
+        parent::__construct();
+    }
+
+    /**
+     * Shows the last revisions for this page.
+     * @throws NotFoundException
+     */
+    public function index(string $bookSlug, string $pageSlug)
+    {
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
+        $this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()]));
+        return view('pages.revisions', [
+            'page' => $page,
+            'current' => $page
+        ]);
+    }
+
+    /**
+     * Shows a preview of a single revision.
+     * @throws NotFoundException
+     */
+    public function show(string $bookSlug, string $pageSlug, int $revisionId)
+    {
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
+        $revision = $page->revisions()->where('id', '=', $revisionId)->first();
+        if ($revision === null) {
+            throw new NotFoundException();
+        }
+
+        $page->fill($revision->toArray());
+
+        $this->setPageTitle(trans('entities.pages_revision_named', ['pageName' => $page->getShortName()]));
+        return view('pages.revision', [
+            'page' => $page,
+            'book' => $page->book,
+            'diff' => null,
+            'revision' => $revision
+        ]);
+    }
+
+    /**
+     * Shows the changes of a single revision.
+     * @throws NotFoundException
+     */
+    public function changes(string $bookSlug, string $pageSlug, int $revisionId)
+    {
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
+        $revision = $page->revisions()->where('id', '=', $revisionId)->first();
+        if ($revision === null) {
+            throw new NotFoundException();
+        }
+
+        $prev = $revision->getPrevious();
+        $prevContent = $prev->html ?? '';
+        $diff = (new Htmldiff)->diff($prevContent, $revision->html);
+
+        $page->fill($revision->toArray());
+        $this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()]));
+
+        return view('pages.revision', [
+            'page' => $page,
+            'book' => $page->book,
+            'diff' => $diff,
+            'revision' => $revision
+        ]);
+    }
+
+    /**
+     * Restores a page using the content of the specified revision.
+     * @throws NotFoundException
+     */
+    public function restore(string $bookSlug, string $pageSlug, int $revisionId)
+    {
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
+        $this->checkOwnablePermission('page-update', $page);
+
+        $page = $this->pageRepo->restoreRevision($page, $revisionId);
+
+        Activity::add($page, 'page_restore', $page->book->id);
+        return redirect($page->getUrl());
+    }
+
+    /**
+     * Deletes a revision using the id of the specified revision.
+     * @throws NotFoundException
+     */
+    public function destroy(string $bookSlug, string $pageSlug, int $revId)
+    {
+        $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
+        $this->checkOwnablePermission('page-delete', $page);
+
+        $revision = $page->revisions()->where('id', '=', $revId)->first();
+        if ($revision === null) {
+            throw new NotFoundException("Revision #{$revId} not found");
+        }
+
+        // Get the current revision for the page
+        $currentRevision = $page->getCurrentRevision();
+
+        // Check if its the latest revision, cannot delete latest revision.
+        if (intval($currentRevision->id) === intval($revId)) {
+            $this->showErrorNotification(trans('entities.revision_cannot_delete_latest'));
+            return redirect($page->getUrl('/revisions'));
+        }
+
+        $revision->delete();
+        $this->showSuccessNotification(trans('entities.revision_delete_success'));
+        return redirect($page->getUrl('/revisions'));
+    }
+}
diff --git a/app/Http/Controllers/PageTemplateController.php b/app/Http/Controllers/PageTemplateController.php
new file mode 100644 (file)
index 0000000..eaa1a8a
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+namespace BookStack\Http\Controllers;
+
+use BookStack\Entities\Repos\PageRepo;
+use BookStack\Exceptions\NotFoundException;
+use Illuminate\Http\Request;
+
+class PageTemplateController extends Controller
+{
+    protected $pageRepo;
+
+    /**
+     * PageTemplateController constructor
+     */
+    public function __construct(PageRepo $pageRepo)
+    {
+        $this->pageRepo = $pageRepo;
+        parent::__construct();
+    }
+
+    /**
+     * Fetch a list of templates from the system.
+     */
+    public function list(Request $request)
+    {
+        $page = $request->get('page', 1);
+        $search = $request->get('search', '');
+        $templates = $this->pageRepo->getTemplates(10, $page, $search);
+
+        if ($search) {
+            $templates->appends(['search' => $search]);
+        }
+
+        return view('pages.template-manager-list', [
+            'templates' => $templates
+        ]);
+    }
+
+    /**
+     * Get the content of a template.
+     * @throws NotFoundException
+     */
+    public function get(int $templateId)
+    {
+        $page = $this->pageRepo->getById($templateId);
+
+        if (!$page->template) {
+            throw new NotFoundException();
+        }
+
+        return response()->json([
+            'html' => $page->html,
+            'markdown' => $page->markdown,
+        ]);
+    }
+}
index 9893d59935ff6d284d98d5e335e2b452089de15e..148ae5cd65a3049e421ea2b331f3bfa34f31ed4d 100644 (file)
@@ -53,7 +53,7 @@ class PermissionController extends Controller
         ]);
 
         $this->permissionsRepo->saveNewRole($request->all());
-        session()->flash('success', trans('settings.role_create_success'));
+        $this->showSuccessNotification(trans('settings.role_create_success'));
         return redirect('/settings/roles');
     }
 
@@ -75,12 +75,13 @@ class PermissionController extends Controller
 
     /**
      * Updates a user role.
-     * @param $id
      * @param Request $request
+     * @param $id
      * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
      * @throws PermissionsException
+     * @throws \Illuminate\Validation\ValidationException
      */
-    public function updateRole($id, Request $request)
+    public function updateRole(Request $request, $id)
     {
         $this->checkPermission('user-roles-manage');
         $this->validate($request, [
@@ -89,7 +90,7 @@ class PermissionController extends Controller
         ]);
 
         $this->permissionsRepo->updateRole($id, $request->all());
-        session()->flash('success', trans('settings.role_update_success'));
+        $this->showSuccessNotification(trans('settings.role_update_success'));
         return redirect('/settings/roles');
     }
 
@@ -112,22 +113,22 @@ class PermissionController extends Controller
     /**
      * Delete a role from the system,
      * Migrate from a previous role if set.
-     * @param $id
      * @param Request $request
+     * @param $id
      * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
      */
-    public function deleteRole($id, Request $request)
+    public function deleteRole(Request $request, $id)
     {
         $this->checkPermission('user-roles-manage');
 
         try {
             $this->permissionsRepo->deleteRole($id, $request->get('migrate_role_id'));
         } catch (PermissionsException $e) {
-            session()->flash('error', $e->getMessage());
+            $this->showErrorNotification($e->getMessage());
             return redirect()->back();
         }
 
-        session()->flash('success', trans('settings.role_delete_success'));
+        $this->showSuccessNotification(trans('settings.role_delete_success'));
         return redirect('/settings/roles');
     }
 }
index 1691ee9b0bf937f338436305baa8b9beea3bb24f..a5cd7ad6b82224f094173f1d76b2d4a864b48174 100644 (file)
@@ -1,35 +1,27 @@
 <?php namespace BookStack\Http\Controllers;
 
 use BookStack\Actions\ViewService;
-use BookStack\Entities\EntityContextManager;
-use BookStack\Entities\Repos\EntityRepo;
+use BookStack\Entities\Book;
+use BookStack\Entities\Bookshelf;
+use BookStack\Entities\Entity;
+use BookStack\Entities\Managers\EntityContext;
 use BookStack\Entities\SearchService;
-use BookStack\Exceptions\NotFoundException;
-use Illuminate\Contracts\View\Factory;
 use Illuminate\Http\Request;
-use Illuminate\View\View;
 
 class SearchController extends Controller
 {
-    protected $entityRepo;
     protected $viewService;
     protected $searchService;
     protected $entityContextManager;
 
     /**
      * SearchController constructor.
-     * @param EntityRepo $entityRepo
-     * @param ViewService $viewService
-     * @param SearchService $searchService
-     * @param EntityContextManager $entityContextManager
      */
     public function __construct(
-        EntityRepo $entityRepo,
         ViewService $viewService,
         SearchService $searchService,
-        EntityContextManager $entityContextManager
+        EntityContext $entityContextManager
     ) {
-        $this->entityRepo = $entityRepo;
         $this->viewService = $viewService;
         $this->searchService = $searchService;
         $this->entityContextManager = $entityContextManager;
@@ -38,9 +30,6 @@ class SearchController extends Controller
 
     /**
      * Searches all entities.
-     * @param Request $request
-     * @return View
-     * @internal param string $searchTerm
      */
     public function search(Request $request)
     {
@@ -64,12 +53,8 @@ class SearchController extends Controller
 
     /**
      * Searches all entities within a book.
-     * @param Request $request
-     * @param integer $bookId
-     * @return View
-     * @internal param string $searchTerm
      */
-    public function searchBook(Request $request, $bookId)
+    public function searchBook(Request $request, int $bookId)
     {
         $term = $request->get('term', '');
         $results = $this->searchService->searchBook($bookId, $term);
@@ -78,12 +63,8 @@ class SearchController extends Controller
 
     /**
      * Searches all entities within a chapter.
-     * @param Request $request
-     * @param integer $chapterId
-     * @return View
-     * @internal param string $searchTerm
      */
-    public function searchChapter(Request $request, $chapterId)
+    public function searchChapter(Request $request, int $chapterId)
     {
         $term = $request->get('term', '');
         $results = $this->searchService->searchChapter($chapterId, $term);
@@ -93,8 +74,6 @@ class SearchController extends Controller
     /**
      * Search for a list of entities and return a partial HTML response of matching entities.
      * Returns the most popular entities if no search is provided.
-     * @param Request $request
-     * @return mixed
      */
     public function searchEntitiesAjax(Request $request)
     {
@@ -115,15 +94,13 @@ class SearchController extends Controller
 
     /**
      * Search siblings items in the system.
-     * @param Request $request
-     * @return Factory|View|mixed
      */
     public function searchSiblings(Request $request)
     {
         $type = $request->get('entity_type', null);
         $id = $request->get('entity_id', null);
 
-        $entity = $this->entityRepo->getById($type, $id);
+        $entity = Entity::getEntityInstance($type)->newQuery()->visible()->find($id);
         if (!$entity) {
             return $this->jsonError(trans('errors.entity_not_found'), 404);
         }
@@ -132,12 +109,12 @@ class SearchController extends Controller
 
         // Page in chapter
         if ($entity->isA('page') && $entity->chapter) {
-            $entities = $this->entityRepo->getChapterChildren($entity->chapter);
+            $entities = $entity->chapter->visiblePages();
         }
 
         // Page in book or chapter
         if (($entity->isA('page') && !$entity->chapter) || $entity->isA('chapter')) {
-            $entities = $this->entityRepo->getBookDirectChildren($entity->book);
+            $entities = $entity->book->getDirectChildren();
         }
 
         // Book
@@ -145,15 +122,15 @@ class SearchController extends Controller
         if ($entity->isA('book')) {
             $contextShelf = $this->entityContextManager->getContextualShelfForBook($entity);
             if ($contextShelf) {
-                $entities = $this->entityRepo->getBookshelfChildren($contextShelf);
+                $entities = $contextShelf->visibleBooks()->get();
             } else {
-                $entities = $this->entityRepo->getAll('book');
+                $entities = Book::visible()->get();
             }
         }
 
         // Shelve
         if ($entity->isA('bookshelf')) {
-            $entities = $this->entityRepo->getAll('bookshelf');
+            $entities = Bookshelf::visible()->get();
         }
 
         return view('partials.entity-list-basic', ['entities' => $entities, 'style' => 'compact']);
index 650833c7f33bd7a9a5d5e6e97a4446688c22b775..f0a078300654861de6026ccf5ce9929f456976a0 100644 (file)
@@ -1,6 +1,7 @@
 <?php namespace BookStack\Http\Controllers;
 
 use BookStack\Auth\User;
+use BookStack\Notifications\TestEmail;
 use BookStack\Uploads\ImageRepo;
 use BookStack\Uploads\ImageService;
 use Illuminate\Http\Request;
@@ -47,7 +48,7 @@ class SettingController extends Controller
      */
     public function update(Request $request)
     {
-        $this->preventAccessForDemoUsers();
+        $this->preventAccessInDemoMode();
         $this->checkPermission('settings-manage');
         $this->validate($request, [
             'app_logo' => $this->imageRepo->getImageValidationRules(),
@@ -76,7 +77,7 @@ class SettingController extends Controller
             setting()->remove('app-logo');
         }
 
-        session()->flash('success', trans('settings.settings_save_success'));
+        $this->showSuccessNotification(trans('settings.settings_save_success'));
         return redirect('/settings');
     }
 
@@ -111,16 +112,32 @@ class SettingController extends Controller
         $imagesToDelete = $imageService->deleteUnusedImages($checkRevisions, $dryRun);
         $deleteCount = count($imagesToDelete);
         if ($deleteCount === 0) {
-            session()->flash('warning', trans('settings.maint_image_cleanup_nothing_found'));
+            $this->showWarningNotification(trans('settings.maint_image_cleanup_nothing_found'));
             return redirect('/settings/maintenance')->withInput();
         }
 
         if ($dryRun) {
             session()->flash('cleanup-images-warning', trans('settings.maint_image_cleanup_warning', ['count' => $deleteCount]));
         } else {
-            session()->flash('success', trans('settings.maint_image_cleanup_success', ['count' => $deleteCount]));
+            $this->showSuccessNotification(trans('settings.maint_image_cleanup_success', ['count' => $deleteCount]));
         }
 
         return redirect('/settings/maintenance#image-cleanup')->withInput();
     }
+
+    /**
+     * Action to send a test e-mail to the current user.
+     * @param Request $request
+     * @param User $user
+     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+     */
+    public function sendTestEmail(Request $request)
+    {
+        $this->checkPermission('settings-manage');
+
+        user()->notify(new TestEmail());
+        $this->showSuccessNotification(trans('settings.maint_send_test_email_success', ['address' => user()->email]));
+
+        return redirect('/settings/maintenance#image-cleanup')->withInput();
+    }
 }
index 8191fbfe276226ab70bed45825d470b353905b88..b55398d2f6f644cb35cec75383df75be9dd40b71 100644 (file)
@@ -1,30 +1,35 @@
 <?php namespace BookStack\Http\Controllers;
 
 use BookStack\Auth\Access\SocialAuthService;
+use BookStack\Auth\Access\UserInviteService;
 use BookStack\Auth\User;
 use BookStack\Auth\UserRepo;
 use BookStack\Exceptions\UserUpdateException;
 use BookStack\Uploads\ImageRepo;
 use Illuminate\Http\Request;
 use Illuminate\Http\Response;
+use Illuminate\Support\Str;
 
 class UserController extends Controller
 {
 
     protected $user;
     protected $userRepo;
+    protected $inviteService;
     protected $imageRepo;
 
     /**
      * UserController constructor.
      * @param User $user
      * @param UserRepo $userRepo
+     * @param UserInviteService $inviteService
      * @param ImageRepo $imageRepo
      */
-    public function __construct(User $user, UserRepo $userRepo, ImageRepo $imageRepo)
+    public function __construct(User $user, UserRepo $userRepo, UserInviteService $inviteService, ImageRepo $imageRepo)
     {
         $this->user = $user;
         $this->userRepo = $userRepo;
+        $this->inviteService = $inviteService;
         $this->imageRepo = $imageRepo;
         parent::__construct();
     }
@@ -75,8 +80,10 @@ class UserController extends Controller
         ];
 
         $authMethod = config('auth.method');
-        if ($authMethod === 'standard') {
-            $validationRules['password'] = 'required|min:5';
+        $sendInvite = ($request->get('send_invite', 'false') === 'true');
+
+        if ($authMethod === 'standard' && !$sendInvite) {
+            $validationRules['password'] = 'required|min:6';
             $validationRules['password-confirm'] = 'required|same:password';
         } elseif ($authMethod === 'ldap') {
             $validationRules['external_auth_id'] = 'required';
@@ -86,13 +93,17 @@ class UserController extends Controller
         $user = $this->user->fill($request->all());
 
         if ($authMethod === 'standard') {
-            $user->password = bcrypt($request->get('password'));
+            $user->password = bcrypt($request->get('password', Str::random(32)));
         } elseif ($authMethod === 'ldap') {
             $user->external_auth_id = $request->get('external_auth_id');
         }
 
         $user->save();
 
+        if ($sendInvite) {
+            $this->inviteService->sendInvitation($user);
+        }
+
         if ($request->filled('roles')) {
             $roles = $request->get('roles');
             $this->userRepo->setUserRoles($user, $roles);
@@ -133,20 +144,25 @@ class UserController extends Controller
      */
     public function update(Request $request, $id)
     {
-        $this->preventAccessForDemoUsers();
+        $this->preventAccessInDemoMode();
         $this->checkPermissionOrCurrentUser('users-manage', $id);
 
         $this->validate($request, [
             'name'             => 'min:2',
             'email'            => 'min:2|email|unique:users,email,' . $id,
-            'password'         => 'min:5|required_with:password_confirm',
+            'password'         => 'min:6|required_with:password_confirm',
             'password-confirm' => 'same:password|required_with:password',
             'setting'          => 'array',
             'profile_image'    => $this->imageRepo->getImageValidationRules(),
         ]);
 
         $user = $this->userRepo->getById($id);
-        $user->fill($request->all());
+        $user->fill($request->except(['email']));
+
+        // Email updates
+        if (userCan('users-manage') && $request->filled('email')) {
+            $user->email = $request->get('email');
+        }
 
         // Role updates
         if (userCan('users-manage') && $request->filled('roles')) {
@@ -161,7 +177,7 @@ class UserController extends Controller
         }
 
         // External auth id updates
-        if ($this->currentUser->can('users-manage') && $request->filled('external_auth_id')) {
+        if (user()->can('users-manage') && $request->filled('external_auth_id')) {
             $user->external_auth_id = $request->get('external_auth_id');
         }
 
@@ -186,7 +202,7 @@ class UserController extends Controller
         }
 
         $user->save();
-        session()->flash('success', trans('settings.users_edit_success'));
+        $this->showSuccessNotification(trans('settings.users_edit_success'));
 
         $redirectUrl = userCan('users-manage') ? '/settings/users' : ('/settings/users/' . $user->id);
         return redirect($redirectUrl);
@@ -214,23 +230,23 @@ class UserController extends Controller
      */
     public function destroy($id)
     {
-        $this->preventAccessForDemoUsers();
+        $this->preventAccessInDemoMode();
         $this->checkPermissionOrCurrentUser('users-manage', $id);
 
         $user = $this->userRepo->getById($id);
 
         if ($this->userRepo->isOnlyAdmin($user)) {
-            session()->flash('error', trans('errors.users_cannot_delete_only_admin'));
+            $this->showErrorNotification(trans('errors.users_cannot_delete_only_admin'));
             return redirect($user->getEditUrl());
         }
 
         if ($user->system_name === 'public') {
-            session()->flash('error', trans('errors.users_cannot_delete_guest'));
+            $this->showErrorNotification(trans('errors.users_cannot_delete_guest'));
             return redirect($user->getEditUrl());
         }
 
         $this->userRepo->destroy($user);
-        session()->flash('success', trans('settings.users_delete_success'));
+        $this->showSuccessNotification(trans('settings.users_delete_success'));
 
         return redirect('/settings/users');
     }
@@ -245,7 +261,7 @@ class UserController extends Controller
         $user = $this->userRepo->getById($id);
 
         $userActivity = $this->userRepo->getActivity($user);
-        $recentlyCreated = $this->userRepo->getRecentlyCreated($user, 5, 0);
+        $recentlyCreated = $this->userRepo->getRecentlyCreated($user, 5);
         $assetCounts = $this->userRepo->getAssetCounts($user);
 
         return view('users.profile', [
@@ -258,22 +274,22 @@ class UserController extends Controller
 
     /**
      * Update the user's preferred book-list display setting.
-     * @param $id
      * @param Request $request
+     * @param $id
      * @return \Illuminate\Http\RedirectResponse
      */
-    public function switchBookView($id, Request $request)
+    public function switchBookView(Request $request, $id)
     {
         return $this->switchViewType($id, $request, 'books');
     }
 
     /**
      * Update the user's preferred shelf-list display setting.
-     * @param $id
      * @param Request $request
+     * @param $id
      * @return \Illuminate\Http\RedirectResponse
      */
-    public function switchShelfView($id, Request $request)
+    public function switchShelfView(Request $request, $id)
     {
         return $this->switchViewType($id, $request, 'bookshelves');
     }
@@ -303,12 +319,12 @@ class UserController extends Controller
 
     /**
      * Change the stored sort type for a particular view.
+     * @param Request $request
      * @param string $id
      * @param string $type
-     * @param Request $request
      * @return \Illuminate\Http\RedirectResponse
      */
-    public function changeSort(string $id, string $type, Request $request)
+    public function changeSort(Request $request, string $id, string $type)
     {
         $validSortTypes = ['books', 'bookshelves'];
         if (!in_array($type, $validSortTypes)) {
@@ -319,12 +335,12 @@ class UserController extends Controller
 
     /**
      * Update the stored section expansion preference for the given user.
+     * @param Request $request
      * @param string $id
      * @param string $key
-     * @param Request $request
      * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
      */
-    public function updateExpansionPreference(string $id, string $key, Request $request)
+    public function updateExpansionPreference(Request $request, string $id, string $key)
     {
         $this->checkPermissionOrCurrentUser('users-manage', $id);
         $keyWhitelist = ['home-details'];
index 7794f340151c43071e2504900ecdc46b1e3443f5..027e469c805c6e81a7464a9525905035db7ac53f 100644 (file)
@@ -12,7 +12,7 @@ class Kernel extends HttpKernel
      * @var array
      */
     protected $middleware = [
-        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
+        \BookStack\Http\Middleware\CheckForMaintenanceMode::class,
         \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
         \BookStack\Http\Middleware\TrimStrings::class,
         \BookStack\Http\Middleware\TrustProxies::class,
@@ -29,9 +29,11 @@ class Kernel extends HttpKernel
             \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
             \Illuminate\Session\Middleware\StartSession::class,
             \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+            \Illuminate\Routing\Middleware\ThrottleRequests::class,
             \BookStack\Http\Middleware\VerifyCsrfToken::class,
             \Illuminate\Routing\Middleware\SubstituteBindings::class,
-            \BookStack\Http\Middleware\Localization::class
+            \BookStack\Http\Middleware\Localization::class,
+            \BookStack\Http\Middleware\GlobalViewData::class,
         ],
         'api' => [
             'throttle:60,1',
diff --git a/app/Http/Middleware/CheckForMaintenanceMode.php b/app/Http/Middleware/CheckForMaintenanceMode.php
new file mode 100644 (file)
index 0000000..0c76838
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+namespace BookStack\Http\Middleware;
+
+use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
+
+class CheckForMaintenanceMode extends Middleware
+{
+    /**
+     * The URIs that should be reachable while maintenance mode is enabled.
+     *
+     * @var array
+     */
+    protected $except = [
+        //
+    ];
+}
diff --git a/app/Http/Middleware/GlobalViewData.php b/app/Http/Middleware/GlobalViewData.php
new file mode 100644 (file)
index 0000000..bc132df
--- /dev/null
@@ -0,0 +1,27 @@
+<?php namespace BookStack\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+
+/**
+ * Class GlobalViewData
+ * Sets up data that is accessible to any view rendered by the web routes.
+ */
+class GlobalViewData
+{
+
+    /**
+     * Handle an incoming request.
+     *
+     * @param Request $request
+     * @param Closure $next
+     * @return mixed
+     */
+    public function handle(Request $request, Closure $next)
+    {
+        view()->share('signedIn', auth()->check());
+        view()->share('currentUser', user());
+
+        return $next($request);
+    }
+}
index 07852bb00f735a8f265a80c8ac0c7a204a193ba6..de48cb19619273ba60eceac771ff79657197d9af 100644 (file)
@@ -27,7 +27,7 @@ class Localization
         'fr' => 'fr_FR',
         'it' => 'it_IT',
         'ja' => 'ja',
-        'kr' => 'ko_KR',
+        'ko' => 'ko_KR',
         'nl' => 'nl_NL',
         'pl' => 'pl_PL',
         'pt_BR' => 'pt_BR',
@@ -37,6 +37,7 @@ class Localization
         'uk' => 'uk_UA',
         'zh_CN' => 'zh_CN',
         'zh_TW' => 'zh_TW',
+        'tr' => 'tr_TR',
     ];
 
     /**
@@ -57,6 +58,8 @@ class Localization
             $locale = setting()->getUser(user(), 'language', $defaultLang);
         }
 
+        config()->set('app.lang', str_replace('_', '-', $this->getLocaleIso($locale)));
+
         // Set text direction
         if (in_array($locale, $this->rtlLocales)) {
             config()->set('app.rtl', true);
@@ -86,6 +89,16 @@ class Localization
         return $default;
     }
 
+    /**
+     * Get the ISO version of a BookStack language name
+     * @param  string $locale
+     * @return string
+     */
+    public function getLocaleIso(string $locale)
+    {
+        return $this->localeMap[$locale] ?? $locale;
+    }
+
     /**
      * Set the system date locale for localized date formatting.
      * Will try both the standard locale name and the UTF8 variant.
@@ -93,7 +106,7 @@ class Localization
      */
     protected function setSystemDateLocale(string $locale)
     {
-        $systemLocale = $this->localeMap[$locale] ?? $locale;
+        $systemLocale = $this->getLocaleIso($locale);
         $set = setlocale(LC_TIME, $systemLocale);
         if ($set === false) {
             setlocale(LC_TIME, $systemLocale . '.utf8');
index 73c11a82769d861ed0ab1793bd7eb7473466d74b..878c2f1647e968cfdeb91ce985cb7d5f7f088c78 100644 (file)
@@ -16,17 +16,11 @@ class TrustProxies extends Middleware
     protected $proxies;
 
     /**
-     * The current proxy header mappings.
+     * The headers that should be used to detect proxies.
      *
-     * @var array
+     * @var int
      */
-    protected $headers = [
-        Request::HEADER_FORWARDED => 'FORWARDED',
-        Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
-        Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
-        Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
-        Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
-    ];
+    protected $headers = Request::HEADER_X_FORWARDED_ALL;
 
     /**
      * Handle the request, Set the correct user-configured proxy information.
index 291b8326f36d4657e46197117e6d61bfc84762a6..1a29a2b1d121f35f56dfd9e2eeb5611c84c4b42a 100644 (file)
@@ -6,6 +6,13 @@ use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
 
 class VerifyCsrfToken extends Middleware
 {
+    /**
+     * Indicates whether the XSRF-TOKEN cookie should be set on the response.
+     *
+     * @var bool
+     */
+    protected $addHttpCookie = true;
+
     /**
      * The URIs that should be excluded from CSRF verification.
      *
index bd2761a0b5cfcd9fe8f8dec6149ddf0a3a9e5315..183686f67b4eef3a6299c2cb2e25893b4f4c4a61 100644 (file)
@@ -22,5 +22,4 @@ class Request extends LaravelRequest
 
         return $base;
     }
-
-}
\ No newline at end of file
+}
diff --git a/app/Notifications/TestEmail.php b/app/Notifications/TestEmail.php
new file mode 100644 (file)
index 0000000..7fce1c1
--- /dev/null
@@ -0,0 +1,18 @@
+<?php namespace BookStack\Notifications;
+
+class TestEmail extends MailNotification
+{
+    /**
+     * Get the mail representation of the notification.
+     *
+     * @param  mixed  $notifiable
+     * @return \Illuminate\Notifications\Messages\MailMessage
+     */
+    public function toMail($notifiable)
+    {
+        return $this->newMailMessage()
+                ->subject(trans('settings.maint_send_test_email_mail_subject'))
+                ->greeting(trans('settings.maint_send_test_email_mail_greeting'))
+                ->line(trans('settings.maint_send_test_email_mail_text'));
+    }
+}
diff --git a/app/Notifications/UserInvite.php b/app/Notifications/UserInvite.php
new file mode 100644 (file)
index 0000000..b01911b
--- /dev/null
@@ -0,0 +1,31 @@
+<?php namespace BookStack\Notifications;
+
+class UserInvite extends MailNotification
+{
+    public $token;
+
+    /**
+     * Create a new notification instance.
+     * @param string $token
+     */
+    public function __construct($token)
+    {
+        $this->token = $token;
+    }
+
+    /**
+     * Get the mail representation of the notification.
+     *
+     * @param  mixed  $notifiable
+     * @return \Illuminate\Notifications\Messages\MailMessage
+     */
+    public function toMail($notifiable)
+    {
+        $appName = ['appName' => setting('app-name')];
+        return $this->newMailMessage()
+                ->subject(trans('auth.user_invite_email_subject', $appName))
+                ->greeting(trans('auth.user_invite_email_greeting', $appName))
+                ->line(trans('auth.user_invite_email_text'))
+                ->action(trans('auth.user_invite_email_action'), url('/register/invite/' . $this->token));
+    }
+}
index a2fc673f488fc1de45bb27a0f4a48e2790bc9cf6..3a1b4f42ee63e02118a72d798d8e837f12499018 100644 (file)
@@ -25,7 +25,12 @@ class AppServiceProvider extends ServiceProvider
     public function boot()
     {
         // Set root URL
-        URL::forceRootUrl(config('app.url'));
+        $appUrl = config('app.url');
+        if ($appUrl) {
+            $isHttps = (strpos($appUrl, 'https://') === 0);
+            URL::forceRootUrl($appUrl);
+            URL::forceScheme($isHttps ? 'https' : 'http');
+        }
 
         // Custom validation methods
         Validator::extend('image_extension', function ($attribute, $value, $parameters, $validator) {
@@ -43,7 +48,7 @@ class AppServiceProvider extends ServiceProvider
             return "<?php echo icon($expression); ?>";
         });
 
-        Blade::directive('exposeTranslations', function($expression) {
+        Blade::directive('exposeTranslations', function ($expression) {
             return "<?php \$__env->startPush('translations'); ?>" .
                 "<?php foreach({$expression} as \$key): ?>" .
                 '<meta name="translation" key="<?php echo e($key); ?>" value="<?php echo e(trans($key)); ?>">' . "\n" .
index e7bde5290490fc5d1fc06bf01c0c91c6de2afb55..b4158187cd5fe5225b68aa013d033d67d05edb30 100644 (file)
@@ -4,6 +4,7 @@ namespace BookStack\Providers;
 
 use BookStack\Actions\ActivityService;
 use BookStack\Actions\ViewService;
+use BookStack\Auth\Permissions\PermissionService;
 use BookStack\Settings\SettingService;
 use BookStack\Uploads\ImageService;
 use Illuminate\Support\ServiceProvider;
@@ -27,20 +28,24 @@ class CustomFacadeProvider extends ServiceProvider
      */
     public function register()
     {
-        $this->app->bind('activity', function () {
+        $this->app->singleton('activity', function () {
             return $this->app->make(ActivityService::class);
         });
 
-        $this->app->bind('views', function () {
+        $this->app->singleton('views', function () {
             return $this->app->make(ViewService::class);
         });
 
-        $this->app->bind('setting', function () {
+        $this->app->singleton('setting', function () {
             return $this->app->make(SettingService::class);
         });
 
-        $this->app->bind('images', function () {
+        $this->app->singleton('images', function () {
             return $this->app->make(ImageService::class);
         });
+
+        $this->app->singleton('permissions', function () {
+            return $this->app->make(PermissionService::class);
+        });
     }
 }
index 7d51cc73d0a7af9b303a8112292d3ac18a81f432..9ff607afe914a8bc0d6c2e6fea620a2bba092f90 100644 (file)
@@ -1,31 +1,21 @@
 <?php namespace BookStack\Providers;
 
-use BookStack\Translation\Translator;
+use BookStack\Translation\FileLoader;
+use Illuminate\Translation\TranslationServiceProvider as BaseProvider;
 
-class TranslationServiceProvider extends \Illuminate\Translation\TranslationServiceProvider
+class TranslationServiceProvider extends BaseProvider
 {
+
     /**
-     * Register the service provider.
-     *
+     * Register the translation line loader.
+     * Overrides the default register action from Laravel so a custom loader can be used.
      * @return void
      */
-    public function register()
+    protected function registerLoader()
     {
-        $this->registerLoader();
-
-        $this->app->singleton('translator', function ($app) {
-            $loader = $app['translation.loader'];
-
-            // When registering the translator component, we'll need to set the default
-            // locale as well as the fallback locale. So, we'll grab the application
-            // configuration so we can easily get both of these values from there.
-            $locale = $app['config']['app.locale'];
-
-            $trans = new Translator($loader, $locale);
-
-            $trans->setFallback($app['config']['app.fallback_locale']);
-
-            return $trans;
+        $this->app->singleton('translation.loader', function ($app) {
+            return new FileLoader($app['files'], $app['path.lang']);
         });
     }
-}
+
+}
\ No newline at end of file
diff --git a/app/Translation/FileLoader.php b/app/Translation/FileLoader.php
new file mode 100644 (file)
index 0000000..f0f895d
--- /dev/null
@@ -0,0 +1,30 @@
+<?php namespace BookStack\Translation;
+
+use Illuminate\Translation\FileLoader as BaseLoader;
+
+class FileLoader extends BaseLoader
+{
+    /**
+     * Load the messages for the given locale.
+     * Extends Laravel's translation FileLoader to look in multiple directories
+     * so that we can load in translation overrides from the theme file if wanted.
+     * @param  string  $locale
+     * @param  string  $group
+     * @param  string|null  $namespace
+     * @return array
+     */
+    public function load($locale, $group, $namespace = null)
+    {
+        if ($group === '*' && $namespace === '*') {
+            return $this->loadJsonPaths($locale);
+        }
+
+        if (is_null($namespace) || $namespace === '*') {
+            $themeTranslations = $this->loadPath(theme_path('lang'), $locale, $group);
+            $originalTranslations =  $this->loadPath($this->path, $locale, $group);
+            return array_merge($originalTranslations, $themeTranslations);
+        }
+
+        return $this->loadNamespaced($locale, $group, $namespace);
+    }
+}
\ No newline at end of file
diff --git a/app/Translation/Translator.php b/app/Translation/Translator.php
deleted file mode 100644 (file)
index 032f43f..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php namespace BookStack\Translation;
-
-class Translator extends \Illuminate\Translation\Translator
-{
-
-    /**
-     * Mapping of locales to their base locales
-     * @var array
-     */
-    protected $baseLocaleMap = [
-        'de_informal' => 'de',
-    ];
-
-    /**
-     * Get the translation for a given key.
-     *
-     * @param  string  $key
-     * @param  array   $replace
-     * @param  string  $locale
-     * @return string|array|null
-     */
-    public function trans($key, array $replace = [], $locale = null)
-    {
-        $translation = $this->get($key, $replace, $locale);
-
-        if (is_array($translation)) {
-            $translation = $this->mergeBackupTranslations($translation, $key, $locale);
-        }
-
-        return $translation;
-    }
-
-    /**
-     * Merge the fallback translations, and base translations if existing,
-     * into the provided core key => value array of translations content.
-     * @param array $translationArray
-     * @param string $key
-     * @param null $locale
-     * @return array
-     */
-    protected function mergeBackupTranslations(array $translationArray, string $key, $locale = null)
-    {
-        $fallback = $this->get($key, [], $this->fallback);
-        $baseLocale = $this->getBaseLocale($locale ?? $this->locale);
-        $baseTranslations = $baseLocale ? $this->get($key, [], $baseLocale) : [];
-
-        return array_replace_recursive($fallback, $baseTranslations, $translationArray);
-    }
-
-    /**
-     * Get the array of locales to be checked.
-     *
-     * @param  string|null  $locale
-     * @return array
-     */
-    protected function localeArray($locale)
-    {
-        $primaryLocale = $locale ?: $this->locale;
-        return array_filter([$primaryLocale, $this->getBaseLocale($primaryLocale), $this->fallback]);
-    }
-
-    /**
-     * Get the locale to extend for the given locale.
-     *
-     * @param string $locale
-     * @return string|null
-     */
-    protected function getBaseLocale($locale)
-    {
-        return $this->baseLocaleMap[$locale] ?? null;
-    }
-}
index 8720d3c098e74eb96c0890dc174089de43f8be70..3f0b447df7388a573602396d052bce2e5c5bca7a 100644 (file)
@@ -13,7 +13,7 @@ class Attachment extends Ownable
      */
     public function getFileName()
     {
-        if (str_contains($this->name, '.')) {
+        if (strpos($this->name, '.') !== false) {
             return $this->name;
         }
         return $this->name . '.' . $this->extension;
index 6e875a1e7a35c9cec514ccd3e21316ef04980019..ae4fb6e967160787e1c46dde0e442228386f9af9 100644 (file)
@@ -2,6 +2,7 @@
 
 use BookStack\Exceptions\FileUploadException;
 use Exception;
+use Illuminate\Support\Str;
 use Symfony\Component\HttpFoundation\File\UploadedFile;
 
 class AttachmentService extends UploadService
@@ -185,9 +186,9 @@ class AttachmentService extends UploadService
         $storage = $this->getStorage();
         $basePath = 'uploads/files/' . Date('Y-m-M') . '/';
 
-        $uploadFileName = str_random(16) . '.' . $uploadedFile->getClientOriginalExtension();
+        $uploadFileName = Str::random(16) . '.' . $uploadedFile->getClientOriginalExtension();
         while ($storage->exists($basePath . $uploadFileName)) {
-            $uploadFileName = str_random(3) . $uploadFileName;
+            $uploadFileName = Str::random(3) . $uploadFileName;
         }
 
         $attachmentPath = $basePath . $uploadFileName;
index 860230d00f914169969e40d00efdca77e6f0845d..e7668471bd64c174c858eef0ac6c9c36a16e8880 100644 (file)
@@ -7,6 +7,7 @@ use DB;
 use Exception;
 use Illuminate\Contracts\Cache\Repository as Cache;
 use Illuminate\Contracts\Filesystem\Factory as FileSystem;
+use Illuminate\Support\Str;
 use Intervention\Image\Exception\NotSupportedException;
 use Intervention\Image\ImageManager;
 use phpDocumentor\Reflection\Types\Integer;
@@ -140,12 +141,12 @@ class ImageService extends UploadService
         $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m') . '/';
 
         while ($storage->exists($imagePath . $imageName)) {
-            $imageName = str_random(3) . $imageName;
+            $imageName = Str::random(3) . $imageName;
         }
 
         $fullPath = $imagePath . $imageName;
         if ($secureUploads) {
-            $fullPath = $imagePath . str_random(16) . '-' . $imageName;
+            $fullPath = $imagePath . Str::random(16) . '-' . $imageName;
         }
 
         try {
@@ -220,7 +221,7 @@ class ImageService extends UploadService
 
         $storage->put($thumbFilePath, $thumbData);
         $storage->setVisibility($thumbFilePath, 'public');
-        $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72);
+        $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 60 * 72);
 
         return $this->getPublicUrl($thumbFilePath);
     }
index 9bbfcfbf0fc6535a25cec4750d90006c48d444ea..6211f41be0c4ef1a866a4c043287b2d76c1f3f60 100644 (file)
@@ -12,7 +12,7 @@ use BookStack\Settings\SettingService;
  * @return string
  * @throws Exception
  */
-function versioned_asset($file = '') : string
+function versioned_asset(string $file = ''): string
 {
     static $version = null;
 
@@ -35,7 +35,7 @@ function versioned_asset($file = '') : string
  * Defaults to public 'Guest' user if not logged in.
  * @return User
  */
-function user() : User
+function user(): User
 {
     return auth()->user() ?: User::getDefault();
 }
@@ -44,7 +44,7 @@ function user() : User
  * Check if current user is a signed in user.
  * @return bool
  */
-function signedInUser() : bool
+function signedInUser(): bool
 {
     return auth()->user() && !auth()->user()->isDefault();
 }
@@ -53,7 +53,7 @@ function signedInUser() : bool
  * Check if the current user has general access.
  * @return bool
  */
-function hasAppAccess() : bool
+function hasAppAccess(): bool
 {
     return !auth()->guest() || setting('app-public');
 }
@@ -66,7 +66,7 @@ function hasAppAccess() : bool
  * @param Ownable $ownable
  * @return bool
  */
-function userCan(string $permission, Ownable $ownable = null) : bool
+function userCan(string $permission, Ownable $ownable = null): bool
 {
     if ($ownable === null) {
         return user() && user()->can($permission);
@@ -84,7 +84,7 @@ function userCan(string $permission, Ownable $ownable = null) : bool
  * @param string|null $entityClass
  * @return bool
  */
-function userCanOnAny(string $permission, string $entityClass = null) : bool
+function userCanOnAny(string $permission, string $entityClass = null): bool
 {
     $permissionService = app(PermissionService::class);
     return $permissionService->checkUserHasPermissionOnAnything($permission, $entityClass);
@@ -92,11 +92,11 @@ function userCanOnAny(string $permission, string $entityClass = null) : bool
 
 /**
  * Helper to access system settings.
- * @param $key
- * @param bool $default
+ * @param string $key
+ * @param $default
  * @return bool|string|SettingService
  */
-function setting($key = null, $default = false)
+function setting(string $key = null, $default = false)
 {
     $settingService = resolve(SettingService::class);
     if (is_null($key)) {
@@ -110,7 +110,7 @@ function setting($key = null, $default = false)
  * @param string $path
  * @return string
  */
-function theme_path($path = '') : string
+function theme_path(string $path = ''): string
 {
     $theme = config('view.theme');
     if (!$theme) {
@@ -130,18 +130,19 @@ function theme_path($path = '') : string
  * @param array $attrs
  * @return mixed
  */
-function icon($name, $attrs = [])
+function icon(string $name, array $attrs = []): string
 {
     $attrs = array_merge([
-        'class' => 'svg-icon',
-        'data-icon' => $name
+        'class'     => 'svg-icon',
+        'data-icon' => $name,
+        'role'      => 'presentation',
     ], $attrs);
     $attrString = ' ';
     foreach ($attrs as $attrName => $attr) {
         $attrString .=  $attrName . '="' . $attr . '" ';
     }
 
-    $iconPath = resource_path('assets/icons/' . $name . '.svg');
+    $iconPath = resource_path('icons/' . $name . '.svg');
     $themeIconPath = theme_path('icons/' . $name . '.svg');
     if ($themeIconPath && file_exists($themeIconPath)) {
         $iconPath = $themeIconPath;
@@ -157,12 +158,12 @@ function icon($name, $attrs = [])
  * Generate a url with multiple parameters for sorting purposes.
  * Works out the logic to set the correct sorting direction
  * Discards empty parameters and allows overriding.
- * @param $path
+ * @param string $path
  * @param array $data
  * @param array $overrideData
  * @return string
  */
-function sortUrl($path, $data, $overrideData = [])
+function sortUrl(string $path, array $data, array $overrideData = []): string
 {
     $queryStringSections = [];
     $queryData = array_merge($data, $overrideData);
index 516980cc10c745783b9668fcf7d144ffa683556b..6538aa81c50789b32194f77ddb4e7108dafe407c 100644 (file)
@@ -11,8 +11,8 @@
 |
 */
 
-$app = new \BookStack\Application(
-    realpath(__DIR__.'/../')
+$app = new BookStack\Application(
+    dirname(__DIR__)
 );
 
 /*
index 457ce5093710bbcf6dd6550577759ed838951e72..1d952a0c50ac3b560f8a75858917bd414e95e919 100644 (file)
@@ -5,47 +5,48 @@
     "license": "MIT",
     "type": "project",
     "require": {
-        "php": ">=7.0.5",
+        "php": "^7.2",
+        "ext-curl": "*",
+        "ext-dom": "*",
+        "ext-gd": "*",
         "ext-json": "*",
+        "ext-mbstring": "*",
         "ext-tidy": "*",
-        "ext-dom": "*",
         "ext-xml": "*",
-        "ext-mbstring": "*",
-        "ext-gd": "*",
-        "ext-curl": "*",
-        "laravel/framework": "~5.5.44",
-        "fideloper/proxy": "~3.3",
-        "intervention/image": "^2.4",
-        "laravel/socialite": "3.0.x-dev",
+        "barryvdh/laravel-dompdf": "^0.8.5",
+        "barryvdh/laravel-snappy": "^0.4.5",
+        "doctrine/dbal": "^2.9",
+        "fideloper/proxy": "^4.0",
+        "gathercontent/htmldiff": "^0.2.1",
+        "intervention/image": "^2.5",
+        "laravel/framework": "^6.0",
+        "laravel/socialite": "^4.2",
         "league/flysystem-aws-s3-v3": "^1.0",
-        "barryvdh/laravel-dompdf": "^0.8.1",
         "predis/predis": "^1.1",
-        "gathercontent/htmldiff": "^0.2.1",
-        "barryvdh/laravel-snappy": "^0.4.0",
-        "socialiteproviders/slack": "^3.0",
+        "socialiteproviders/discord": "^2.0",
+        "socialiteproviders/gitlab": "^3.0",
         "socialiteproviders/microsoft-azure": "^3.0",
         "socialiteproviders/okta": "^1.0",
-        "socialiteproviders/gitlab": "^3.0",
-        "socialiteproviders/twitch": "^3.0",
-        "socialiteproviders/discord": "^2.0",
-        "doctrine/dbal": "^2.5",
+        "socialiteproviders/slack": "^3.0",
+        "socialiteproviders/twitch": "^5.0",
         "aacotroneo/laravel-saml2": "^1.0"
     },
     "require-dev": {
-        "filp/whoops": "~2.0",
-        "fzaninotto/faker": "~1.4",
-        "mockery/mockery": "~1.0",
-        "phpunit/phpunit": "~6.0",
-        "symfony/css-selector": "3.1.*",
-        "symfony/dom-crawler": "3.1.*",
-        "laravel/browser-kit-testing": "^2.0",
-        "barryvdh/laravel-ide-helper": "^2.4.1",
-        "barryvdh/laravel-debugbar": "^3.1.0",
-        "squizlabs/php_codesniffer": "^3.2"
+        "barryvdh/laravel-debugbar": "^3.2.8",
+        "barryvdh/laravel-ide-helper": "^2.6.4",
+        "facade/ignition": "^1.4",
+        "fzaninotto/faker": "^1.4",
+        "laravel/browser-kit-testing": "^5.1",
+        "mockery/mockery": "^1.0",
+        "nunomaduro/collision": "^3.0",
+        "phpunit/phpunit": "^8.0",
+        "squizlabs/php_codesniffer": "^3.4",
+        "wnx/laravel-stats": "^2.0"
     },
     "autoload": {
         "classmap": [
-            "database"
+            "database/seeds",
+            "database/factories"
         ],
         "psr-4": {
             "BookStack\\": "app/"
     },
     "scripts": {
         "post-root-package-install": [
-            "php -r \"file_exists('.env') || copy('.env.example', '.env');\""
+            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
         ],
         "post-create-project-cmd": [
-            "php artisan key:generate"
+            "@php artisan key:generate --ansi"
         ],
         "pre-update-cmd": [
-            "php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"",
-            "php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\""
+            "@php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"",
+            "@php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\""
         ],
         "pre-install-cmd": [
-            "php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"",
-            "php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\""
+            "@php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"",
+            "@php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\""
         ],
         "post-install-cmd": [
-            "php artisan cache:clear",
-            "php artisan view:clear"
+            "@php artisan cache:clear",
+            "@php artisan view:clear"
         ],
         "post-autoload-dump": [
             "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
-            "@php artisan package:discover"
+            "@php artisan package:discover --ansi"
         ],
         "refresh-test-database": [
-            "php artisan migrate:refresh --database=mysql_testing",
-            "php artisan db:seed --class=DummyContentSeeder --database=mysql_testing"
+            "@php artisan migrate:refresh --database=mysql_testing",
+            "@php artisan db:seed --class=DummyContentSeeder --database=mysql_testing"
         ]
     },
     "config": {
         "optimize-autoloader": true,
         "preferred-install": "dist",
+        "sort-packages": true,
         "platform": {
-            "php": "7.0.5"
+            "php": "7.2.0"
+        }
+    },
+    "extra": {
+        "laravel": {
+            "dont-discover": []
         }
-    }
+    },
+    "minimum-stability": "dev",
+    "prefer-stable": true
 }
index d355946429e85740ae99434f98d01d1c406abf4e..3ec106ded4f0c57c9ca3556222dff16874606bc8 100644 (file)
@@ -1,89 +1,29 @@
 {
     "_readme": [
         "This file locks the dependencies of your project to a known state",
-        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "26a2c3ad0409c970f4f0c9b6dad49322",
+    "content-hash": "c156e1738dbab2a57f9a926d9a9a5a6a",
     "packages": [
-        {
-            "name": "aacotroneo/laravel-saml2",
-            "version": "1.0.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/aacotroneo/laravel-saml2.git",
-                "reference": "5045701a07bcd7600a17c92971368669870f546a"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/aacotroneo/laravel-saml2/zipball/5045701a07bcd7600a17c92971368669870f546a",
-                "reference": "5045701a07bcd7600a17c92971368669870f546a",
-                "shasum": ""
-            },
-            "require": {
-                "ext-openssl": "*",
-                "illuminate/support": ">=5.0.0",
-                "onelogin/php-saml": "^3.0.0",
-                "php": ">=5.4.0"
-            },
-            "require-dev": {
-                "mockery/mockery": "0.9.*"
-            },
-            "type": "library",
-            "extra": {
-                "laravel": {
-                    "providers": [
-                        "Aacotroneo\\Saml2\\Saml2ServiceProvider"
-                    ],
-                    "aliases": {
-                        "Saml2": "Aacotroneo\\Saml2\\Facades\\Saml2Auth"
-                    }
-                }
-            },
-            "autoload": {
-                "psr-0": {
-                    "Aacotroneo\\Saml2\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "aacotroneo",
-                    "email": "[email protected]"
-                }
-            ],
-            "description": "A Laravel package for Saml2 integration as a SP (service provider) based on OneLogin toolkit, which is much lightweight than simplesamlphp",
-            "homepage": "https://github.com/aacotroneo/laravel-saml2",
-            "keywords": [
-                "SAML2",
-                "laravel",
-                "onelogin",
-                "saml"
-            ],
-            "time": "2018-11-08T14:03:58+00:00"
-        },
         {
             "name": "aws/aws-sdk-php",
-            "version": "3.86.2",
+            "version": "3.112.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/aws/aws-sdk-php.git",
-                "reference": "50224232ac7a4e2a6fa4ebbe0281e5b7503acf76"
+                "reference": "1e21446c6780a3b9b5e4315bd6d4347d2c3381eb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/50224232ac7a4e2a6fa4ebbe0281e5b7503acf76",
-                "reference": "50224232ac7a4e2a6fa4ebbe0281e5b7503acf76",
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/1e21446c6780a3b9b5e4315bd6d4347d2c3381eb",
+                "reference": "1e21446c6780a3b9b5e4315bd6d4347d2c3381eb",
                 "shasum": ""
             },
             "require": {
                 "ext-json": "*",
                 "ext-pcre": "*",
                 "ext-simplexml": "*",
-                "ext-spl": "*",
                 "guzzlehttp/guzzle": "^5.3.3|^6.2.1",
                 "guzzlehttp/promises": "~1.0",
                 "guzzlehttp/psr7": "^1.4.1",
                 "ext-sockets": "*",
                 "nette/neon": "^2.3",
                 "phpunit/phpunit": "^4.8.35|^5.4.3",
-                "psr/cache": "^1.0"
+                "psr/cache": "^1.0",
+                "psr/simple-cache": "^1.0"
             },
             "suggest": {
                 "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
                 "s3",
                 "sdk"
             ],
-            "time": "2019-01-18T21:10:44+00:00"
+            "time": "2019-09-12T18:09:53+00:00"
         },
         {
             "name": "barryvdh/laravel-dompdf",
-            "version": "v0.8.3",
+            "version": "v0.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/barryvdh/laravel-dompdf.git",
-                "reference": "46781d0304277845a19c09c169bc595fd182cce4"
+                "reference": "7393732b2f3a3ee357974cbb0c46c9b65b84dad1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/46781d0304277845a19c09c169bc595fd182cce4",
-                "reference": "46781d0304277845a19c09c169bc595fd182cce4",
+                "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/7393732b2f3a3ee357974cbb0c46c9b65b84dad1",
+                "reference": "7393732b2f3a3ee357974cbb0c46c9b65b84dad1",
                 "shasum": ""
             },
             "require": {
                 "dompdf/dompdf": "^0.8",
-                "illuminate/support": "5.5.x|5.6.x|5.7.x",
+                "illuminate/support": "^5.5|^6",
                 "php": ">=7"
             },
             "type": "library",
                 "laravel",
                 "pdf"
             ],
-            "time": "2018-08-31T13:25:44+00:00"
+            "time": "2019-08-23T14:30:33+00:00"
         },
         {
             "name": "barryvdh/laravel-snappy",
-            "version": "v0.4.3",
+            "version": "v0.4.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/barryvdh/laravel-snappy.git",
-                "reference": "62bb5017b7004bf3e48bfed3d5c00d3dc6e60478"
+                "reference": "9be767fc7a082665a84945f36c70b0cbead91ce9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/barryvdh/laravel-snappy/zipball/62bb5017b7004bf3e48bfed3d5c00d3dc6e60478",
-                "reference": "62bb5017b7004bf3e48bfed3d5c00d3dc6e60478",
+                "url": "https://api.github.com/repos/barryvdh/laravel-snappy/zipball/9be767fc7a082665a84945f36c70b0cbead91ce9",
+                "reference": "9be767fc7a082665a84945f36c70b0cbead91ce9",
                 "shasum": ""
             },
             "require": {
-                "illuminate/filesystem": "5.5.x|5.6.x|5.7.x",
-                "illuminate/support": "5.5.x|5.6.x|5.7.x",
+                "illuminate/filesystem": "5.5.x|5.6.x|5.7.x|5.8.x|6.0.*",
+                "illuminate/support": "5.5.x|5.6.x|5.7.x|5.8.x|6.0.*",
                 "knplabs/knp-snappy": "^1",
                 "php": ">=7"
             },
                     "email": "[email protected]"
                 }
             ],
-            "description": "Snappy PDF/Image for Laravel 4",
+            "description": "Snappy PDF/Image for Laravel",
             "keywords": [
                 "image",
                 "laravel",
                 "wkhtmltoimage",
                 "wkhtmltopdf"
             ],
-            "time": "2018-09-06T10:14:15+00:00"
+            "time": "2019-08-30T16:12:23+00:00"
         },
         {
             "name": "cogpowered/finediff",
             ],
             "time": "2014-05-19T10:25:02+00:00"
         },
-        {
-            "name": "doctrine/annotations",
-            "version": "v1.4.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/doctrine/annotations.git",
-                "reference": "54cacc9b81758b14e3ce750f205a393d52339e97"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97",
-                "reference": "54cacc9b81758b14e3ce750f205a393d52339e97",
-                "shasum": ""
-            },
-            "require": {
-                "doctrine/lexer": "1.*",
-                "php": "^5.6 || ^7.0"
-            },
-            "require-dev": {
-                "doctrine/cache": "1.*",
-                "phpunit/phpunit": "^5.7"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.4.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Roman Borschel",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Benjamin Eberlei",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Jonathan Wage",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Johannes Schmitt",
-                    "email": "[email protected]"
-                }
-            ],
-            "description": "Docblock Annotations Parser",
-            "homepage": "http://www.doctrine-project.org",
-            "keywords": [
-                "annotations",
-                "docblock",
-                "parser"
-            ],
-            "time": "2017-02-24T16:22:25+00:00"
-        },
         {
             "name": "doctrine/cache",
-            "version": "v1.6.2",
+            "version": "v1.8.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/cache.git",
-                "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b"
+                "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/cache/zipball/eb152c5100571c7a45470ff2a35095ab3f3b900b",
-                "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b",
+                "url": "https://api.github.com/repos/doctrine/cache/zipball/d768d58baee9a4862ca783840eca1b9add7a7f57",
+                "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57",
                 "shasum": ""
             },
             "require": {
-                "php": "~5.5|~7.0"
+                "php": "~7.1"
             },
             "conflict": {
                 "doctrine/common": ">2.2,<2.4"
             },
             "require-dev": {
-                "phpunit/phpunit": "~4.8|~5.0",
-                "predis/predis": "~1.0",
-                "satooshi/php-coveralls": "~0.6"
+                "alcaeus/mongo-php-adapter": "^1.1",
+                "doctrine/coding-standard": "^4.0",
+                "mongodb/mongodb": "^1.1",
+                "phpunit/phpunit": "^7.0",
+                "predis/predis": "~1.0"
+            },
+            "suggest": {
+                "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.6.x-dev"
+                    "dev-master": "1.8.x-dev"
                 }
             },
             "autoload": {
                 }
             ],
             "description": "Caching library offering an object-oriented API for many cache backends",
-            "homepage": "http://www.doctrine-project.org",
+            "homepage": "https://www.doctrine-project.org",
             "keywords": [
                 "cache",
                 "caching"
             ],
-            "time": "2017-07-22T12:49:21+00:00"
+            "time": "2018-08-21T18:01:43+00:00"
         },
         {
-            "name": "doctrine/collections",
-            "version": "v1.4.0",
+            "name": "doctrine/dbal",
+            "version": "v2.9.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/doctrine/collections.git",
-                "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba"
+                "url": "https://github.com/doctrine/dbal.git",
+                "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba",
-                "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba",
+                "url": "https://api.github.com/repos/doctrine/dbal/zipball/22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9",
+                "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.6 || ^7.0"
+                "doctrine/cache": "^1.0",
+                "doctrine/event-manager": "^1.0",
+                "ext-pdo": "*",
+                "php": "^7.1"
             },
             "require-dev": {
-                "doctrine/coding-standard": "~0.1@dev",
-                "phpunit/phpunit": "^5.7"
+                "doctrine/coding-standard": "^5.0",
+                "jetbrains/phpstorm-stubs": "^2018.1.2",
+                "phpstan/phpstan": "^0.10.1",
+                "phpunit/phpunit": "^7.4",
+                "symfony/console": "^2.0.5|^3.0|^4.0",
+                "symfony/phpunit-bridge": "^3.4.5|^4.0.5"
+            },
+            "suggest": {
+                "symfony/console": "For helpful console commands such as SQL execution and import of files."
             },
+            "bin": [
+                "bin/doctrine-dbal"
+            ],
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.3.x-dev"
+                    "dev-master": "2.9.x-dev",
+                    "dev-develop": "3.0.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Doctrine\\Common\\Collections\\": "lib/"
+                "psr-4": {
+                    "Doctrine\\DBAL\\": "lib/Doctrine/DBAL"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 {
                     "name": "Jonathan Wage",
                     "email": "[email protected]"
-                },
-                {
-                    "name": "Johannes Schmitt",
-                    "email": "[email protected]"
                 }
             ],
-            "description": "Collections Abstraction library",
-            "homepage": "http://www.doctrine-project.org",
+            "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.",
+            "homepage": "https://www.doctrine-project.org/projects/dbal.html",
             "keywords": [
-                "array",
-                "collections",
-                "iterator"
+                "abstraction",
+                "database",
+                "dbal",
+                "mysql",
+                "persistence",
+                "pgsql",
+                "php",
+                "queryobject"
             ],
-            "time": "2017-01-03T10:49:41+00:00"
+            "time": "2018-12-31T03:27:51+00:00"
         },
         {
-            "name": "doctrine/common",
-            "version": "v2.7.3",
+            "name": "doctrine/event-manager",
+            "version": "v1.0.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/doctrine/common.git",
-                "reference": "4acb8f89626baafede6ee5475bc5844096eba8a9"
+                "url": "https://github.com/doctrine/event-manager.git",
+                "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/common/zipball/4acb8f89626baafede6ee5475bc5844096eba8a9",
-                "reference": "4acb8f89626baafede6ee5475bc5844096eba8a9",
+                "url": "https://api.github.com/repos/doctrine/event-manager/zipball/a520bc093a0170feeb6b14e9d83f3a14452e64b3",
+                "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3",
                 "shasum": ""
             },
             "require": {
-                "doctrine/annotations": "1.*",
-                "doctrine/cache": "1.*",
-                "doctrine/collections": "1.*",
-                "doctrine/inflector": "1.*",
-                "doctrine/lexer": "1.*",
-                "php": "~5.6|~7.0"
+                "php": "^7.1"
+            },
+            "conflict": {
+                "doctrine/common": "<2.9@dev"
             },
             "require-dev": {
-                "phpunit/phpunit": "^5.4.6"
+                "doctrine/coding-standard": "^4.0",
+                "phpunit/phpunit": "^7.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.7.x-dev"
+                    "dev-master": "1.0.x-dev"
                 }
             },
             "autoload": {
                 {
                     "name": "Johannes Schmitt",
                     "email": "[email protected]"
-                }
-            ],
-            "description": "Common Library for Doctrine projects",
-            "homepage": "http://www.doctrine-project.org",
-            "keywords": [
-                "annotations",
-                "collections",
-                "eventmanager",
-                "persistence",
-                "spl"
-            ],
-            "time": "2017-07-22T08:35:12+00:00"
-        },
-        {
-            "name": "doctrine/dbal",
-            "version": "v2.5.13",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/doctrine/dbal.git",
-                "reference": "729340d8d1eec8f01bff708e12e449a3415af873"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/dbal/zipball/729340d8d1eec8f01bff708e12e449a3415af873",
-                "reference": "729340d8d1eec8f01bff708e12e449a3415af873",
-                "shasum": ""
-            },
-            "require": {
-                "doctrine/common": ">=2.4,<2.8-dev",
-                "php": ">=5.3.2"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "4.*",
-                "symfony/console": "2.*||^3.0"
-            },
-            "suggest": {
-                "symfony/console": "For helpful console commands such as SQL execution and import of files."
-            },
-            "bin": [
-                "bin/doctrine-dbal"
-            ],
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.5.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-0": {
-                    "Doctrine\\DBAL\\": "lib/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Roman Borschel",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Benjamin Eberlei",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "[email protected]"
                 },
                 {
-                    "name": "Jonathan Wage",
-                    "email": "jonwage@gmail.com"
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com"
                 }
             ],
-            "description": "Database Abstraction Layer",
-            "homepage": "http://www.doctrine-project.org",
+            "description": "Doctrine Event Manager component",
+            "homepage": "https://www.doctrine-project.org/projects/event-manager.html",
             "keywords": [
-                "database",
-                "dbal",
-                "persistence",
-                "queryobject"
+                "event",
+                "eventdispatcher",
+                "eventmanager"
             ],
-            "time": "2017-07-22T20:44:48+00:00"
+            "time": "2018-06-11T11:59:03+00:00"
         },
         {
             "name": "doctrine/inflector",
-            "version": "v1.2.0",
+            "version": "v1.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/inflector.git",
-                "reference": "e11d84c6e018beedd929cff5220969a3c6d1d462"
+                "reference": "5527a48b7313d15261292c149e55e26eae771b0a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/inflector/zipball/e11d84c6e018beedd929cff5220969a3c6d1d462",
-                "reference": "e11d84c6e018beedd929cff5220969a3c6d1d462",
+                "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a",
+                "reference": "5527a48b7313d15261292c149e55e26eae771b0a",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.0"
+                "php": "^7.1"
             },
             "require-dev": {
                 "phpunit/phpunit": "^6.2"
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.2.x-dev"
+                    "dev-master": "1.3.x-dev"
                 }
             },
             "autoload": {
                 "singularize",
                 "string"
             ],
-            "time": "2017-07-22T12:18:28+00:00"
+            "time": "2018-01-09T20:05:19+00:00"
         },
         {
             "name": "doctrine/lexer",
-            "version": "v1.0.1",
+            "version": "1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/lexer.git",
-                "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c"
+                "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c",
-                "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c",
+                "url": "https://api.github.com/repos/doctrine/lexer/zipball/e17f069ede36f7534b95adec71910ed1b49c74ea",
+                "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.2"
+                "php": "^7.2"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^6.0",
+                "phpstan/phpstan": "^0.11.8",
+                "phpunit/phpunit": "^8.2"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "1.1.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Doctrine\\Common\\Lexer\\": "lib/"
+                "psr-4": {
+                    "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 "MIT"
             ],
             "authors": [
-                {
-                    "name": "Roman Borschel",
-                    "email": "[email protected]"
-                },
                 {
                     "name": "Guilherme Blanco",
                     "email": "[email protected]"
                 },
+                {
+                    "name": "Roman Borschel",
+                    "email": "[email protected]"
+                },
                 {
                     "name": "Johannes Schmitt",
                     "email": "[email protected]"
                 }
             ],
-            "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.",
-            "homepage": "http://www.doctrine-project.org",
+            "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
+            "homepage": "https://www.doctrine-project.org/projects/lexer.html",
             "keywords": [
+                "annotations",
+                "docblock",
                 "lexer",
-                "parser"
+                "parser",
+                "php"
             ],
-            "time": "2014-09-09T13:34:57+00:00"
+            "time": "2019-07-30T19:33:28+00:00"
         },
         {
             "name": "dompdf/dompdf",
             "time": "2018-12-14T02:40:31+00:00"
         },
         {
-            "name": "egulias/email-validator",
-            "version": "2.1.7",
+            "name": "dragonmantank/cron-expression",
+            "version": "v2.3.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/egulias/EmailValidator.git",
-                "reference": "709f21f92707308cdf8f9bcfa1af4cb26586521e"
+                "url": "https://github.com/dragonmantank/cron-expression.git",
+                "reference": "72b6fbf76adb3cf5bc0db68559b33d41219aba27"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/709f21f92707308cdf8f9bcfa1af4cb26586521e",
-                "reference": "709f21f92707308cdf8f9bcfa1af4cb26586521e",
+                "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/72b6fbf76adb3cf5bc0db68559b33d41219aba27",
+                "reference": "72b6fbf76adb3cf5bc0db68559b33d41219aba27",
                 "shasum": ""
             },
             "require": {
-                "doctrine/lexer": "^1.0.1",
-                "php": ">= 5.5"
+                "php": "^7.0"
             },
             "require-dev": {
-                "dominicsayers/isemail": "dev-master",
-                "phpunit/phpunit": "^4.8.35||^5.7||^6.0",
-                "satooshi/php-coveralls": "^1.0.1"
-            },
-            "suggest": {
-                "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
+                "phpunit/phpunit": "^6.4|^7.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0.x-dev"
+                    "dev-master": "2.3-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Egulias\\EmailValidator\\": "EmailValidator"
+                    "Cron\\": "src/Cron/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             ],
             "authors": [
                 {
-                    "name": "Eduardo Gulias Davis"
+                    "name": "Michael Dowling",
+                    "email": "[email protected]",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Chris Tankersley",
+                    "email": "[email protected]",
+                    "homepage": "https://github.com/dragonmantank"
                 }
             ],
-            "description": "A library for validating emails against several RFCs",
-            "homepage": "https://github.com/egulias/EmailValidator",
+            "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
             "keywords": [
-                "email",
-                "emailvalidation",
-                "emailvalidator",
-                "validation",
-                "validator"
+                "cron",
+                "schedule"
             ],
-            "time": "2018-12-04T22:38:24+00:00"
+            "time": "2019-03-31T00:38:28+00:00"
         },
         {
-            "name": "erusev/parsedown",
-            "version": "1.7.1",
+            "name": "egulias/email-validator",
+            "version": "2.1.11",
             "source": {
                 "type": "git",
-                "url": "https://github.com/erusev/parsedown.git",
-                "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1"
+                "url": "https://github.com/egulias/EmailValidator.git",
+                "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/erusev/parsedown/zipball/92e9c27ba0e74b8b028b111d1b6f956a15c01fc1",
-                "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1",
+                "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/92dd169c32f6f55ba570c309d83f5209cefb5e23",
+                "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23",
                 "shasum": ""
             },
             "require": {
-                "ext-mbstring": "*",
-                "php": ">=5.3.0"
+                "doctrine/lexer": "^1.0.1",
+                "php": ">= 5.5"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35"
-            },
+                "dominicsayers/isemail": "dev-master",
+                "phpunit/phpunit": "^4.8.35||^5.7||^6.0",
+                "satooshi/php-coveralls": "^1.0.1",
+                "symfony/phpunit-bridge": "^4.4@dev"
+            },
+            "suggest": {
+                "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Egulias\\EmailValidator\\": "EmailValidator"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Eduardo Gulias Davis"
+                }
+            ],
+            "description": "A library for validating emails against several RFCs",
+            "homepage": "https://github.com/egulias/EmailValidator",
+            "keywords": [
+                "email",
+                "emailvalidation",
+                "emailvalidator",
+                "validation",
+                "validator"
+            ],
+            "time": "2019-08-13T17:33:27+00:00"
+        },
+        {
+            "name": "erusev/parsedown",
+            "version": "1.7.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/erusev/parsedown.git",
+                "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
+                "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35"
+            },
             "type": "library",
             "autoload": {
                 "psr-0": {
                 "markdown",
                 "parser"
             ],
-            "time": "2018-03-08T01:11:30+00:00"
+            "time": "2019-03-17T18:48:37+00:00"
         },
         {
             "name": "fideloper/proxy",
-            "version": "3.3.4",
+            "version": "4.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/fideloper/TrustedProxy.git",
-                "reference": "9cdf6f118af58d89764249bbcc7bb260c132924f"
+                "reference": "03085e58ec7bee24773fa5a8850751a6e61a7e8a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/9cdf6f118af58d89764249bbcc7bb260c132924f",
-                "reference": "9cdf6f118af58d89764249bbcc7bb260c132924f",
+                "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/03085e58ec7bee24773fa5a8850751a6e61a7e8a",
+                "reference": "03085e58ec7bee24773fa5a8850751a6e61a7e8a",
                 "shasum": ""
             },
             "require": {
-                "illuminate/contracts": "~5.0",
+                "illuminate/contracts": "^5.0|^6.0|^7.0",
                 "php": ">=5.4.0"
             },
             "require-dev": {
-                "illuminate/http": "~5.0",
-                "mockery/mockery": "~0.9.3",
-                "phpunit/phpunit": "^5.7"
+                "illuminate/http": "^5.0|^6.0|^7.0",
+                "mockery/mockery": "^1.0",
+                "phpunit/phpunit": "^6.0"
             },
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-master": "3.3-dev"
-                },
                 "laravel": {
                     "providers": [
                         "Fideloper\\Proxy\\TrustedProxyServiceProvider"
                 "proxy",
                 "trusted proxy"
             ],
-            "time": "2017-06-15T17:19:42+00:00"
+            "time": "2019-09-03T16:45:42+00:00"
         },
         {
             "name": "gathercontent/htmldiff",
         },
         {
             "name": "guzzlehttp/psr7",
-            "version": "1.5.2",
+            "version": "1.6.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/psr7.git",
-                "reference": "9f83dded91781a01c63574e387eaa769be769115"
+                "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115",
-                "reference": "9f83dded91781a01c63574e387eaa769be769115",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
+                "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.4.0",
                 "psr/http-message": "~1.0",
-                "ralouphie/getallheaders": "^2.0.5"
+                "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
             },
             "provide": {
                 "psr/http-message-implementation": "1.0"
             },
             "require-dev": {
+                "ext-zlib": "*",
                 "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
             },
+            "suggest": {
+                "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.5-dev"
+                    "dev-master": "1.6-dev"
                 }
             },
             "autoload": {
                 "uri",
                 "url"
             ],
-            "time": "2018-12-04T20:46:45+00:00"
+            "time": "2019-07-01T23:21:34+00:00"
         },
         {
             "name": "intervention/image",
-            "version": "2.4.2",
+            "version": "2.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Intervention/image.git",
-                "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb"
+                "reference": "39eaef720d082ecc54c64bf54541c55f10db546d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Intervention/image/zipball/e82d274f786e3d4b866a59b173f42e716f0783eb",
-                "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb",
+                "url": "https://api.github.com/repos/Intervention/image/zipball/39eaef720d082ecc54c64bf54541c55f10db546d",
+                "reference": "39eaef720d082ecc54c64bf54541c55f10db546d",
                 "shasum": ""
             },
             "require": {
                 "thumbnail",
                 "watermark"
             ],
-            "time": "2018-05-29T14:19:03+00:00"
+            "time": "2019-06-24T14:06:31+00:00"
         },
         {
             "name": "knplabs/knp-snappy",
-            "version": "v1.0.4",
+            "version": "v1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/KnpLabs/snappy.git",
-                "reference": "144c4ecd1ccaeda936bf832b93079efc490e6850"
+                "reference": "ea037298d3c613454da77ecb9588cf0397d695e1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/KnpLabs/snappy/zipball/144c4ecd1ccaeda936bf832b93079efc490e6850",
-                "reference": "144c4ecd1ccaeda936bf832b93079efc490e6850",
+                "url": "https://api.github.com/repos/KnpLabs/snappy/zipball/ea037298d3c613454da77ecb9588cf0397d695e1",
+                "reference": "ea037298d3c613454da77ecb9588cf0397d695e1",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.6",
+                "php": ">=7.1",
                 "psr/log": "^1.0",
-                "symfony/process": "~2.3 || ~3.0 || ~4.0"
+                "symfony/process": "~3.4||~4.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "~4.8.36"
+                "phpunit/phpunit": "~7.4"
             },
             "suggest": {
                 "h4cc/wkhtmltoimage-amd64": "Provides wkhtmltoimage-amd64 binary for Linux-compatible machines, use version `~0.12` as dependency",
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "1.x-dev"
                 }
             },
             "autoload": {
                 "thumbnail",
                 "wkhtmltopdf"
             ],
-            "time": "2018-01-22T19:40:51+00:00"
+            "time": "2018-12-14T14:59:37+00:00"
         },
         {
             "name": "laravel/framework",
-            "version": "v5.5.44",
+            "version": "v6.0.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/framework.git",
-                "reference": "00615aa27eb98f0ee6fb9f2160c6c60ae04abd1b"
+                "reference": "56789e9dec750e0fbe8e9e6ae90a01a4e6887902"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/framework/zipball/00615aa27eb98f0ee6fb9f2160c6c60ae04abd1b",
-                "reference": "00615aa27eb98f0ee6fb9f2160c6c60ae04abd1b",
+                "url": "https://api.github.com/repos/laravel/framework/zipball/56789e9dec750e0fbe8e9e6ae90a01a4e6887902",
+                "reference": "56789e9dec750e0fbe8e9e6ae90a01a4e6887902",
                 "shasum": ""
             },
             "require": {
-                "doctrine/inflector": "~1.1",
-                "erusev/parsedown": "~1.7",
+                "doctrine/inflector": "^1.1",
+                "dragonmantank/cron-expression": "^2.0",
+                "egulias/email-validator": "^2.1.10",
+                "erusev/parsedown": "^1.7",
+                "ext-json": "*",
                 "ext-mbstring": "*",
                 "ext-openssl": "*",
                 "league/flysystem": "^1.0.8",
-                "monolog/monolog": "~1.12",
-                "mtdowling/cron-expression": "~1.0",
-                "nesbot/carbon": "^1.24.1",
-                "php": ">=7.0",
-                "psr/container": "~1.0",
+                "monolog/monolog": "^1.12|^2.0",
+                "nesbot/carbon": "^2.0",
+                "opis/closure": "^3.1",
+                "php": "^7.2",
+                "psr/container": "^1.0",
                 "psr/simple-cache": "^1.0",
-                "ramsey/uuid": "~3.0",
-                "swiftmailer/swiftmailer": "~6.0",
-                "symfony/console": "~3.3",
-                "symfony/debug": "~3.3",
-                "symfony/finder": "~3.3",
-                "symfony/http-foundation": "~3.3",
-                "symfony/http-kernel": "~3.3",
-                "symfony/process": "~3.3",
-                "symfony/routing": "~3.3",
-                "symfony/var-dumper": "~3.3",
-                "tijsverkoyen/css-to-inline-styles": "~2.2",
-                "vlucas/phpdotenv": "~2.2"
+                "ramsey/uuid": "^3.7",
+                "swiftmailer/swiftmailer": "^6.0",
+                "symfony/console": "^4.3.4",
+                "symfony/debug": "^4.3.4",
+                "symfony/finder": "^4.3.4",
+                "symfony/http-foundation": "^4.3.4",
+                "symfony/http-kernel": "^4.3.4",
+                "symfony/process": "^4.3.4",
+                "symfony/routing": "^4.3.4",
+                "symfony/var-dumper": "^4.3.4",
+                "tijsverkoyen/css-to-inline-styles": "^2.2.1",
+                "vlucas/phpdotenv": "^3.3"
+            },
+            "conflict": {
+                "tightenco/collect": "<5.5.33"
             },
             "replace": {
                 "illuminate/auth": "self.version",
                 "illuminate/support": "self.version",
                 "illuminate/translation": "self.version",
                 "illuminate/validation": "self.version",
-                "illuminate/view": "self.version",
-                "tightenco/collect": "<5.5.33"
+                "illuminate/view": "self.version"
             },
             "require-dev": {
-                "aws/aws-sdk-php": "~3.0",
-                "doctrine/dbal": "~2.5",
-                "filp/whoops": "^2.1.4",
-                "mockery/mockery": "~1.0",
-                "orchestra/testbench-core": "3.5.*",
-                "pda/pheanstalk": "~3.0",
-                "phpunit/phpunit": "~6.0",
+                "aws/aws-sdk-php": "^3.0",
+                "doctrine/dbal": "^2.6",
+                "filp/whoops": "^2.4",
+                "guzzlehttp/guzzle": "^6.3",
+                "league/flysystem-cached-adapter": "^1.0",
+                "mockery/mockery": "^1.2.3",
+                "moontoast/math": "^1.1",
+                "orchestra/testbench-core": "^4.0",
+                "pda/pheanstalk": "^4.0",
+                "phpunit/phpunit": "^8.3",
                 "predis/predis": "^1.1.1",
-                "symfony/css-selector": "~3.3",
-                "symfony/dom-crawler": "~3.3"
+                "symfony/cache": "^4.3",
+                "true/punycode": "^2.1"
             },
             "suggest": {
-                "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).",
-                "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.5).",
+                "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.0).",
+                "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).",
+                "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().",
+                "ext-memcached": "Required to use the memcache cache driver.",
                 "ext-pcntl": "Required to use all features of the queue worker.",
                 "ext-posix": "Required to use all features of the queue worker.",
-                "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).",
-                "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~6.0).",
-                "laravel/tinker": "Required to use the tinker console command (~1.0).",
-                "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).",
-                "league/flysystem-cached-adapter": "Required to use Flysystem caching (~1.0).",
-                "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).",
-                "nexmo/client": "Required to use the Nexmo transport (~1.0).",
-                "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).",
-                "predis/predis": "Required to use the redis cache and queue drivers (~1.0).",
-                "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~3.0).",
-                "symfony/css-selector": "Required to use some of the crawler integration testing tools (~3.3).",
-                "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~3.3).",
-                "symfony/psr-http-message-bridge": "Required to psr7 bridging features (~1.0)."
+                "ext-redis": "Required to use the Redis cache and queue drivers.",
+                "filp/whoops": "Required for friendly error pages in development (^2.4).",
+                "fzaninotto/faker": "Required to use the eloquent factory builder (^1.4).",
+                "guzzlehttp/guzzle": "Required to use the Mailgun mail driver and the ping methods on schedules (^6.0).",
+                "laravel/tinker": "Required to use the tinker console command (^1.0).",
+                "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).",
+                "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).",
+                "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).",
+                "moontoast/math": "Required to use ordered UUIDs (^1.1).",
+                "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).",
+                "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^3.0).",
+                "symfony/cache": "Required to PSR-6 cache bridge (^4.3.4).",
+                "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^1.2).",
+                "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)."
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "5.5-dev"
+                    "dev-master": "6.x-dev"
                 }
             },
             "autoload": {
                 "framework",
                 "laravel"
             ],
-            "time": "2018-10-04T14:51:24+00:00"
+            "time": "2019-09-10T18:46:24+00:00"
         },
         {
             "name": "laravel/socialite",
-            "version": "3.0.x-dev",
+            "version": "v4.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/socialite.git",
-                "reference": "79316f36641f1916a50ab14d368acdf1d97e46de"
+                "reference": "f509d06e1e7323997b804c5152874f8aad4508e9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/socialite/zipball/79316f36641f1916a50ab14d368acdf1d97e46de",
-                "reference": "79316f36641f1916a50ab14d368acdf1d97e46de",
+                "url": "https://api.github.com/repos/laravel/socialite/zipball/f509d06e1e7323997b804c5152874f8aad4508e9",
+                "reference": "f509d06e1e7323997b804c5152874f8aad4508e9",
                 "shasum": ""
             },
             "require": {
+                "ext-json": "*",
                 "guzzlehttp/guzzle": "~6.0",
-                "illuminate/contracts": "~5.4",
-                "illuminate/http": "~5.4",
-                "illuminate/support": "~5.4",
+                "illuminate/http": "~5.7.0|~5.8.0|^6.0|^7.0",
+                "illuminate/support": "~5.7.0|~5.8.0|^6.0|^7.0",
                 "league/oauth1-client": "~1.0",
-                "php": ">=5.6.4"
+                "php": "^7.1.3"
             },
             "require-dev": {
-                "mockery/mockery": "~0.9",
-                "phpunit/phpunit": "~4.0|~5.0"
+                "illuminate/contracts": "~5.7.0|~5.8.0|^6.0|^7.0",
+                "mockery/mockery": "^1.0",
+                "phpunit/phpunit": "^7.0|^8.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.0-dev"
+                    "dev-master": "4.0-dev"
                 },
                 "laravel": {
                     "providers": [
                 "laravel",
                 "oauth"
             ],
-            "time": "2018-12-21T14:06:32+00:00"
+            "time": "2019-09-03T15:27:17+00:00"
         },
         {
             "name": "league/flysystem",
-            "version": "1.0.49",
+            "version": "1.0.55",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/flysystem.git",
-                "reference": "a63cc83d8a931b271be45148fa39ba7156782ffd"
+                "reference": "33c91155537c6dc899eacdc54a13ac6303f156e6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a63cc83d8a931b271be45148fa39ba7156782ffd",
-                "reference": "a63cc83d8a931b271be45148fa39ba7156782ffd",
+                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/33c91155537c6dc899eacdc54a13ac6303f156e6",
+                "reference": "33c91155537c6dc899eacdc54a13ac6303f156e6",
                 "shasum": ""
             },
             "require": {
                 "sftp",
                 "storage"
             ],
-            "time": "2018-11-23T23:41:29+00:00"
+            "time": "2019-08-24T11:17:19+00:00"
         },
         {
             "name": "league/flysystem-aws-s3-v3",
-            "version": "1.0.21",
+            "version": "1.0.23",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
-                "reference": "43523fec10a831ea48bedb3277e3f3fa218f4e49"
+                "reference": "15b0cdeab7240bf8e8bffa85ae5275bbc3692bf4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/43523fec10a831ea48bedb3277e3f3fa218f4e49",
-                "reference": "43523fec10a831ea48bedb3277e3f3fa218f4e49",
+                "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/15b0cdeab7240bf8e8bffa85ae5275bbc3692bf4",
+                "reference": "15b0cdeab7240bf8e8bffa85ae5275bbc3692bf4",
                 "shasum": ""
             },
             "require": {
                 }
             ],
             "description": "Flysystem adapter for the AWS S3 SDK v3.x",
-            "time": "2018-10-08T07:53:55+00:00"
+            "time": "2019-06-05T17:18:29+00:00"
         },
         {
             "name": "league/oauth1-client",
         },
         {
             "name": "monolog/monolog",
-            "version": "1.24.0",
+            "version": "2.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Seldaek/monolog.git",
-                "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266"
+                "reference": "68545165e19249013afd1d6f7485aecff07a2d22"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
-                "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/68545165e19249013afd1d6f7485aecff07a2d22",
+                "reference": "68545165e19249013afd1d6f7485aecff07a2d22",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.0",
-                "psr/log": "~1.0"
+                "php": "^7.2",
+                "psr/log": "^1.0.1"
             },
             "provide": {
                 "psr/log-implementation": "1.0.0"
             "require-dev": {
                 "aws/aws-sdk-php": "^2.4.9 || ^3.0",
                 "doctrine/couchdb": "~1.0@dev",
-                "graylog2/gelf-php": "~1.0",
-                "jakub-onderka/php-parallel-lint": "0.9",
+                "elasticsearch/elasticsearch": "^6.0",
+                "graylog2/gelf-php": "^1.4.2",
+                "jakub-onderka/php-parallel-lint": "^0.9",
                 "php-amqplib/php-amqplib": "~2.4",
                 "php-console/php-console": "^3.1.3",
-                "phpunit/phpunit": "~4.5",
-                "phpunit/phpunit-mock-objects": "2.3.0",
+                "phpspec/prophecy": "^1.6.1",
+                "phpunit/phpunit": "^8.3",
+                "predis/predis": "^1.1",
+                "rollbar/rollbar": "^1.3",
                 "ruflin/elastica": ">=0.90 <3.0",
-                "sentry/sentry": "^0.13",
                 "swiftmailer/swiftmailer": "^5.3|^6.0"
             },
             "suggest": {
                 "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
                 "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+                "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
                 "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
-                "ext-mongo": "Allow sending log messages to a MongoDB server",
+                "ext-mbstring": "Allow to work properly with unicode symbols",
+                "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
                 "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
-                "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
+                "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
                 "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
                 "php-console/php-console": "Allow sending log messages to Google Chrome",
                 "rollbar/rollbar": "Allow sending log messages to Rollbar",
-                "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
-                "sentry/sentry": "Allow sending log messages to a Sentry server"
+                "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0.x-dev"
+                    "dev-master": "2.x-dev"
                 }
             },
             "autoload": {
                 "logging",
                 "psr-3"
             ],
-            "time": "2018-11-05T09:00:11+00:00"
-        },
-        {
-            "name": "mtdowling/cron-expression",
-            "version": "v1.2.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/mtdowling/cron-expression.git",
-                "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/9504fa9ea681b586028adaaa0877db4aecf32bad",
-                "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.2"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.0|~5.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Cron\\": "src/Cron/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Michael Dowling",
-                    "email": "[email protected]",
-                    "homepage": "https://github.com/mtdowling"
-                }
-            ],
-            "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
-            "keywords": [
-                "cron",
-                "schedule"
-            ],
-            "time": "2017-01-23T04:29:33+00:00"
+            "time": "2019-08-30T09:56:44+00:00"
         },
         {
             "name": "mtdowling/jmespath.php",
         },
         {
             "name": "nesbot/carbon",
-            "version": "1.36.2",
+            "version": "2.24.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/briannesbitt/Carbon.git",
-                "reference": "cd324b98bc30290f233dd0e75e6ce49f7ab2a6c9"
+                "reference": "934459c5ac0658bc765ad1e53512c7c77adcac29"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/cd324b98bc30290f233dd0e75e6ce49f7ab2a6c9",
-                "reference": "cd324b98bc30290f233dd0e75e6ce49f7ab2a6c9",
+                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/934459c5ac0658bc765ad1e53512c7c77adcac29",
+                "reference": "934459c5ac0658bc765ad1e53512c7c77adcac29",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.9",
-                "symfony/translation": "~2.6 || ~3.0 || ~4.0"
+                "ext-json": "*",
+                "php": "^7.1.8 || ^8.0",
+                "symfony/translation": "^3.4 || ^4.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.7"
-            },
-            "suggest": {
-                "friendsofphp/php-cs-fixer": "Needed for the `composer phpcs` command. Allow to automatically fix code style.",
-                "phpstan/phpstan": "Needed for the `composer phpstan` command. Allow to detect potential errors."
+                "friendsofphp/php-cs-fixer": "^2.14 || ^3.0",
+                "kylekatarnls/multi-tester": "^1.1",
+                "phpmd/phpmd": "dev-php-7.1-compatibility",
+                "phpstan/phpstan": "^0.11",
+                "phpunit/phpunit": "^7.5 || ^8.0",
+                "squizlabs/php_codesniffer": "^3.4"
             },
+            "bin": [
+                "bin/carbon"
+            ],
             "type": "library",
             "extra": {
                 "laravel": {
             },
             "autoload": {
                 "psr-4": {
-                    "": "src/"
+                    "Carbon\\": "src/Carbon/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                     "name": "Brian Nesbitt",
                     "email": "[email protected]",
                     "homepage": "http://nesbot.com"
+                },
+                {
+                    "name": "kylekatarnls",
+                    "homepage": "http://github.com/kylekatarnls"
                 }
             ],
-            "description": "A simple API extension for DateTime.",
+            "description": "A API extension for DateTime that supports 281 different languages.",
             "homepage": "http://carbon.nesbot.com",
             "keywords": [
                 "date",
                 "datetime",
                 "time"
             ],
-            "time": "2018-12-28T10:07:33+00:00"
+            "time": "2019-08-31T16:37:55+00:00"
         },
         {
-            "name": "onelogin/php-saml",
-            "version": "3.2.1",
+            "name": "opis/closure",
+            "version": "3.4.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/onelogin/php-saml.git",
-                "reference": "845a6ce39e839ed9e687f80bffb02ffde16a70d0"
+                "url": "https://github.com/opis/closure.git",
+                "reference": "60a97fff133b1669a5b1776aa8ab06db3f3962b7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/onelogin/php-saml/zipball/845a6ce39e839ed9e687f80bffb02ffde16a70d0",
-                "reference": "845a6ce39e839ed9e687f80bffb02ffde16a70d0",
+                "url": "https://api.github.com/repos/opis/closure/zipball/60a97fff133b1669a5b1776aa8ab06db3f3962b7",
+                "reference": "60a97fff133b1669a5b1776aa8ab06db3f3962b7",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.4",
-                "robrichards/xmlseclibs": ">=3.0.3"
+                "php": "^5.4 || ^7.0"
             },
             "require-dev": {
-                "pdepend/pdepend": "^2.5.0",
-                "php-coveralls/php-coveralls": "^1.0.2 || ^2.0",
-                "phploc/phploc": "^2.1 || ^3.0 || ^4.0",
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1",
-                "sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0",
-                "squizlabs/php_codesniffer": "^3.1.1"
-            },
-            "suggest": {
-                "ext-curl": "Install curl lib to be able to use the IdPMetadataParser for parsing remote XMLs",
-                "ext-gettext": "Install gettext and php5-gettext libs to handle translations",
-                "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)"
+                "jeremeamia/superclosure": "^2.0",
+                "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.3.x-dev"
+                }
+            },
             "autoload": {
                 "psr-4": {
-                    "OneLogin\\": "src/"
-                }
+                    "Opis\\Closure\\": "src/"
+                },
+                "files": [
+                    "functions.php"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "MIT"
             ],
-            "description": "OneLogin PHP SAML Toolkit",
-            "homepage": "https://developers.onelogin.com/saml/php",
-            "keywords": [
-                "SAML2",
-                "onelogin",
-                "saml"
+            "authors": [
+                {
+                    "name": "Marius Sarca",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Sorin Sarca",
+                    "email": "[email protected]"
+                }
             ],
-            "time": "2019-06-25T10:28:20+00:00"
+            "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.",
+            "homepage": "https://opis.io/closure",
+            "keywords": [
+                "anonymous functions",
+                "closure",
+                "function",
+                "serializable",
+                "serialization",
+                "serialize"
+            ],
+            "time": "2019-09-02T21:07:33+00:00"
         },
         {
             "name": "paragonie/random_compat",
         },
         {
             "name": "phenx/php-svg-lib",
-            "version": "v0.3.2",
+            "version": "v0.3.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/PhenX/php-svg-lib.git",
-                "reference": "ccc46ef6340d4b8a4a68047e68d8501ea961442c"
+                "reference": "5fa61b65e612ce1ae15f69b3d223cb14ecc60e32"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/ccc46ef6340d4b8a4a68047e68d8501ea961442c",
-                "reference": "ccc46ef6340d4b8a4a68047e68d8501ea961442c",
+                "url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/5fa61b65e612ce1ae15f69b3d223cb14ecc60e32",
+                "reference": "5fa61b65e612ce1ae15f69b3d223cb14ecc60e32",
                 "shasum": ""
             },
             "require": {
-                "sabberworm/php-css-parser": "8.1.*"
+                "sabberworm/php-css-parser": "^8.3"
             },
             "require-dev": {
-                "phpunit/phpunit": "~5.0"
+                "phpunit/phpunit": "^5.5|^6.5"
             },
             "type": "library",
             "autoload": {
-                "psr-0": {
-                    "Svg\\": "src/"
+                "psr-4": {
+                    "Svg\\": "src/Svg"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             ],
             "description": "A library to read, parse and export to PDF SVG files.",
             "homepage": "https://github.com/PhenX/php-svg-lib",
-            "time": "2018-06-03T10:10:03+00:00"
+            "time": "2019-09-11T20:02:13+00:00"
+        },
+        {
+            "name": "phpoption/phpoption",
+            "version": "1.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/schmittjoh/php-option.git",
+                "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed",
+                "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.7.*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "PhpOption\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache2"
+            ],
+            "authors": [
+                {
+                    "name": "Johannes M. Schmitt",
+                    "email": "[email protected]"
+                }
+            ],
+            "description": "Option Type for PHP",
+            "keywords": [
+                "language",
+                "option",
+                "php",
+                "type"
+            ],
+            "time": "2015-07-25T16:39:46+00:00"
         },
         {
             "name": "predis/predis",
         },
         {
             "name": "ralouphie/getallheaders",
-            "version": "2.0.5",
+            "version": "3.0.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/ralouphie/getallheaders.git",
-                "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa"
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
-                "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3"
+                "php": ">=5.6"
             },
             "require-dev": {
-                "phpunit/phpunit": "~3.7.0",
-                "satooshi/php-coveralls": ">=1.0"
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
             },
             "type": "library",
             "autoload": {
                 }
             ],
             "description": "A polyfill for getallheaders.",
-            "time": "2016-02-11T07:05:27+00:00"
+            "time": "2019-03-08T08:55:37+00:00"
         },
         {
             "name": "ramsey/uuid",
             ],
             "time": "2018-07-19T23:38:55+00:00"
         },
-        {
-            "name": "robrichards/xmlseclibs",
-            "version": "3.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/robrichards/xmlseclibs.git",
-                "reference": "406c68ac9124db033d079284b719958b829cb830"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/406c68ac9124db033d079284b719958b829cb830",
-                "reference": "406c68ac9124db033d079284b719958b829cb830",
-                "shasum": ""
-            },
-            "require": {
-                "ext-openssl": "*",
-                "php": ">= 5.4"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "RobRichards\\XMLSecLibs\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "description": "A PHP library for XML Security",
-            "homepage": "https://github.com/robrichards/xmlseclibs",
-            "keywords": [
-                "security",
-                "signature",
-                "xml",
-                "xmldsig"
-            ],
-            "time": "2018-11-15T11:59:02+00:00"
-        },
         {
             "name": "sabberworm/php-css-parser",
-            "version": "8.1.0",
+            "version": "8.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
-                "reference": "850cbbcbe7fbb155387a151ea562897a67e242ef"
+                "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/850cbbcbe7fbb155387a151ea562897a67e242ef",
-                "reference": "850cbbcbe7fbb155387a151ea562897a67e242ef",
+                "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f",
+                "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.2"
             },
             "require-dev": {
-                "phpunit/phpunit": "*"
+                "codacy/coverage": "^1.4",
+                "phpunit/phpunit": "~4.8"
             },
             "type": "library",
             "autoload": {
                 "parser",
                 "stylesheet"
             ],
-            "time": "2016-07-19T19:14:21+00:00"
+            "time": "2019-02-22T07:42:52+00:00"
         },
         {
             "name": "socialiteproviders/discord",
         },
         {
             "name": "socialiteproviders/manager",
-            "version": "v3.3.4",
+            "version": "v3.4.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/SocialiteProviders/Manager.git",
-                "reference": "58b72a667da292a1d0a0b1e6e9aeda4053617030"
+                "reference": "e3e8e78b9a3060801cd008941a0894a0a0c479e1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/58b72a667da292a1d0a0b1e6e9aeda4053617030",
-                "reference": "58b72a667da292a1d0a0b1e6e9aeda4053617030",
+                "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/e3e8e78b9a3060801cd008941a0894a0a0c479e1",
+                "reference": "e3e8e78b9a3060801cd008941a0894a0a0c479e1",
                 "shasum": ""
             },
             "require": {
+                "illuminate/support": "~5.4|~5.7.0|~5.8.0|^6.0",
                 "laravel/socialite": "~3.0|~4.0",
                 "php": "^5.6 || ^7.0"
             },
                 {
                     "name": "Anton Komarev",
                     "email": "[email protected]"
+                },
+                {
+                    "name": "Miguel Piedrafita",
+                    "email": "[email protected]"
                 }
             ],
             "description": "Easily add new or override built-in providers in Laravel Socialite.",
-            "time": "2019-01-16T07:58:54+00:00"
+            "time": "2019-09-09T03:07:52+00:00"
         },
         {
             "name": "socialiteproviders/microsoft-azure",
         },
         {
             "name": "socialiteproviders/twitch",
-            "version": "v3.0.0",
+            "version": "v5.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/SocialiteProviders/Twitch.git",
-                "reference": "a7ad148c0b42d0c607d8a034b6e47faf5fc85e93"
+                "reference": "8c19b26ff24c40cc019413042a5492c5ed21a658"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/SocialiteProviders/Twitch/zipball/a7ad148c0b42d0c607d8a034b6e47faf5fc85e93",
-                "reference": "a7ad148c0b42d0c607d8a034b6e47faf5fc85e93",
+                "url": "https://api.github.com/repos/SocialiteProviders/Twitch/zipball/8c19b26ff24c40cc019413042a5492c5ed21a658",
+                "reference": "8c19b26ff24c40cc019413042a5492c5ed21a658",
                 "shasum": ""
             },
             "require": {
                 "php": "^5.6 || ^7.0",
-                "socialiteproviders/manager": "~3.0"
+                "socialiteproviders/manager": "~2.0 || ~3.0"
             },
             "type": "library",
             "autoload": {
                 }
             ],
             "description": "Twitch OAuth2 Provider for Laravel Socialite",
-            "time": "2017-01-25T09:48:29+00:00"
+            "time": "2018-06-20T10:59:51+00:00"
         },
         {
             "name": "swiftmailer/swiftmailer",
-            "version": "v6.1.3",
+            "version": "v6.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/swiftmailer/swiftmailer.git",
-                "reference": "8ddcb66ac10c392d3beb54829eef8ac1438595f4"
+                "reference": "5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8ddcb66ac10c392d3beb54829eef8ac1438595f4",
-                "reference": "8ddcb66ac10c392d3beb54829eef8ac1438595f4",
+                "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a",
+                "reference": "5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a",
                 "shasum": ""
             },
             "require": {
                 "egulias/email-validator": "~2.0",
-                "php": ">=7.0.0"
+                "php": ">=7.0.0",
+                "symfony/polyfill-iconv": "^1.0",
+                "symfony/polyfill-intl-idn": "^1.10",
+                "symfony/polyfill-mbstring": "^1.0"
             },
             "require-dev": {
                 "mockery/mockery": "~0.9.1",
-                "symfony/phpunit-bridge": "~3.3@dev"
+                "symfony/phpunit-bridge": "^3.4.19|^4.1.8"
             },
             "suggest": {
                 "ext-intl": "Needed to support internationalized email addresses",
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "6.1-dev"
+                    "dev-master": "6.2-dev"
                 }
             },
             "autoload": {
                 "mail",
                 "mailer"
             ],
-            "time": "2018-09-11T07:12:52+00:00"
+            "time": "2019-04-21T09:21:45+00:00"
         },
         {
             "name": "symfony/console",
-            "version": "v3.3.6",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "b0878233cb5c4391347e5495089c7af11b8e6201"
+                "reference": "de63799239b3881b8a08f8481b22348f77ed7b36"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/b0878233cb5c4391347e5495089c7af11b8e6201",
-                "reference": "b0878233cb5c4391347e5495089c7af11b8e6201",
+                "url": "https://api.github.com/repos/symfony/console/zipball/de63799239b3881b8a08f8481b22348f77ed7b36",
+                "reference": "de63799239b3881b8a08f8481b22348f77ed7b36",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
-                "symfony/debug": "~2.8|~3.0",
-                "symfony/polyfill-mbstring": "~1.0"
+                "php": "^7.1.3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php73": "^1.8",
+                "symfony/service-contracts": "^1.1"
             },
             "conflict": {
-                "symfony/dependency-injection": "<3.3"
+                "symfony/dependency-injection": "<3.4",
+                "symfony/event-dispatcher": "<4.3",
+                "symfony/process": "<3.3"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0"
             },
             "require-dev": {
                 "psr/log": "~1.0",
-                "symfony/config": "~3.3",
-                "symfony/dependency-injection": "~3.3",
-                "symfony/event-dispatcher": "~2.8|~3.0",
-                "symfony/filesystem": "~2.8|~3.0",
-                "symfony/http-kernel": "~2.8|~3.0",
-                "symfony/process": "~2.8|~3.0"
+                "symfony/config": "~3.4|~4.0",
+                "symfony/dependency-injection": "~3.4|~4.0",
+                "symfony/event-dispatcher": "^4.3",
+                "symfony/lock": "~3.4|~4.0",
+                "symfony/process": "~3.4|~4.0",
+                "symfony/var-dumper": "^4.3"
             },
             "suggest": {
                 "psr/log": "For using the console logger",
                 "symfony/event-dispatcher": "",
-                "symfony/filesystem": "",
+                "symfony/lock": "",
                 "symfony/process": ""
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2017-07-29T21:27:59+00:00"
+            "time": "2019-08-26T08:26:39+00:00"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v3.1.10",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "722a87478a72d95dc2a3bcf41dc9c2d13fd4cb2d"
+                "reference": "c6e5e2a00db768c92c3ae131532af4e1acc7bd03"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/722a87478a72d95dc2a3bcf41dc9c2d13fd4cb2d",
-                "reference": "722a87478a72d95dc2a3bcf41dc9c2d13fd4cb2d",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/c6e5e2a00db768c92c3ae131532af4e1acc7bd03",
+                "reference": "c6e5e2a00db768c92c3ae131532af4e1acc7bd03",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^7.1.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
                 "MIT"
             ],
             "authors": [
-                {
-                    "name": "Jean-François Simon",
-                    "email": "[email protected]"
-                },
                 {
                     "name": "Fabien Potencier",
                     "email": "[email protected]"
                 },
+                {
+                    "name": "Jean-François Simon",
+                    "email": "[email protected]"
+                },
                 {
                     "name": "Symfony Community",
                     "homepage": "https://symfony.com/contributors"
             ],
             "description": "Symfony CssSelector Component",
             "homepage": "https://symfony.com",
-            "time": "2017-01-02T20:31:54+00:00"
+            "time": "2019-08-20T14:07:54+00:00"
         },
         {
             "name": "symfony/debug",
-            "version": "v3.3.6",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
-                "reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13"
+                "reference": "afcdea44a2e399c1e4b52246ec8d54c715393ced"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/debug/zipball/7c13ae8ce1e2adbbd574fc39de7be498e1284e13",
-                "reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/afcdea44a2e399c1e4b52246ec8d54c715393ced",
+                "reference": "afcdea44a2e399c1e4b52246ec8d54c715393ced",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^7.1.3",
                 "psr/log": "~1.0"
             },
             "conflict": {
-                "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
+                "symfony/http-kernel": "<3.4"
             },
             "require-dev": {
-                "symfony/http-kernel": "~2.8|~3.0"
+                "symfony/http-kernel": "~3.4|~4.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Debug Component",
             "homepage": "https://symfony.com",
-            "time": "2017-07-28T15:27:31+00:00"
+            "time": "2019-08-20T14:27:59+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v3.3.6",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e"
+                "reference": "429d0a1451d4c9c4abe1959b2986b88794b9b7d2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67535f1e3fd662bdc68d7ba317c93eecd973617e",
-                "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/429d0a1451d4c9c4abe1959b2986b88794b9b7d2",
+                "reference": "429d0a1451d4c9c4abe1959b2986b88794b9b7d2",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^7.1.3",
+                "symfony/event-dispatcher-contracts": "^1.1"
             },
             "conflict": {
-                "symfony/dependency-injection": "<3.3"
+                "symfony/dependency-injection": "<3.4"
+            },
+            "provide": {
+                "psr/event-dispatcher-implementation": "1.0",
+                "symfony/event-dispatcher-implementation": "1.1"
             },
             "require-dev": {
                 "psr/log": "~1.0",
-                "symfony/config": "~2.8|~3.0",
-                "symfony/dependency-injection": "~3.3",
-                "symfony/expression-language": "~2.8|~3.0",
-                "symfony/stopwatch": "~2.8|~3.0"
+                "symfony/config": "~3.4|~4.0",
+                "symfony/dependency-injection": "~3.4|~4.0",
+                "symfony/expression-language": "~3.4|~4.0",
+                "symfony/http-foundation": "^3.4|^4.0",
+                "symfony/service-contracts": "^1.1",
+                "symfony/stopwatch": "~3.4|~4.0"
             },
             "suggest": {
                 "symfony/dependency-injection": "",
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2017-06-09T14:53:08+00:00"
+            "time": "2019-08-26T08:55:16+00:00"
+        },
+        {
+            "name": "symfony/event-dispatcher-contracts",
+            "version": "v1.1.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+                "reference": "c61766f4440ca687de1084a5c00b08e167a2575c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c61766f4440ca687de1084a5c00b08e167a2575c",
+                "reference": "c61766f4440ca687de1084a5c00b08e167a2575c",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3"
+            },
+            "suggest": {
+                "psr/event-dispatcher": "",
+                "symfony/event-dispatcher-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\EventDispatcher\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to dispatching event",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "time": "2019-06-20T06:46:26+00:00"
         },
         {
             "name": "symfony/finder",
-            "version": "v3.3.6",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/finder.git",
-                "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4"
+                "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4",
-                "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/86c1c929f0a4b24812e1eb109262fc3372c8e9f2",
+                "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^7.1.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Finder Component",
             "homepage": "https://symfony.com",
-            "time": "2017-06-01T21:01:25+00:00"
+            "time": "2019-08-14T12:26:46+00:00"
         },
         {
             "name": "symfony/http-foundation",
-            "version": "v3.3.6",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-foundation.git",
-                "reference": "49e8cd2d59a7aa9bfab19e46de680c76e500a031"
+                "reference": "d804bea118ff340a12e22a79f9c7e7eb56b35adc"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/49e8cd2d59a7aa9bfab19e46de680c76e500a031",
-                "reference": "49e8cd2d59a7aa9bfab19e46de680c76e500a031",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d804bea118ff340a12e22a79f9c7e7eb56b35adc",
+                "reference": "d804bea118ff340a12e22a79f9c7e7eb56b35adc",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^7.1.3",
+                "symfony/mime": "^4.3",
                 "symfony/polyfill-mbstring": "~1.1"
             },
             "require-dev": {
-                "symfony/expression-language": "~2.8|~3.0"
+                "predis/predis": "~1.0",
+                "symfony/expression-language": "~3.4|~4.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony HttpFoundation Component",
             "homepage": "https://symfony.com",
-            "time": "2017-07-21T11:04:46+00:00"
+            "time": "2019-08-26T08:55:16+00:00"
         },
         {
             "name": "symfony/http-kernel",
-            "version": "v3.3.6",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-kernel.git",
-                "reference": "db10d05f1d95e4168e638db7a81c79616f568ea5"
+                "reference": "5e0fc71be03d52cd00c423061cfd300bd6f92a52"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/db10d05f1d95e4168e638db7a81c79616f568ea5",
-                "reference": "db10d05f1d95e4168e638db7a81c79616f568ea5",
+                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/5e0fc71be03d52cd00c423061cfd300bd6f92a52",
+                "reference": "5e0fc71be03d52cd00c423061cfd300bd6f92a52",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^7.1.3",
                 "psr/log": "~1.0",
-                "symfony/debug": "~2.8|~3.0",
-                "symfony/event-dispatcher": "~2.8|~3.0",
-                "symfony/http-foundation": "~3.3"
+                "symfony/debug": "~3.4|~4.0",
+                "symfony/event-dispatcher": "^4.3",
+                "symfony/http-foundation": "^4.1.1",
+                "symfony/polyfill-ctype": "~1.8",
+                "symfony/polyfill-php73": "^1.9"
             },
             "conflict": {
-                "symfony/config": "<2.8",
-                "symfony/dependency-injection": "<3.3",
-                "symfony/var-dumper": "<3.3",
+                "symfony/browser-kit": "<4.3",
+                "symfony/config": "<3.4",
+                "symfony/dependency-injection": "<4.3",
+                "symfony/translation": "<4.2",
+                "symfony/var-dumper": "<4.1.1",
                 "twig/twig": "<1.34|<2.4,>=2"
             },
+            "provide": {
+                "psr/log-implementation": "1.0"
+            },
             "require-dev": {
                 "psr/cache": "~1.0",
-                "symfony/browser-kit": "~2.8|~3.0",
-                "symfony/class-loader": "~2.8|~3.0",
-                "symfony/config": "~2.8|~3.0",
-                "symfony/console": "~2.8|~3.0",
-                "symfony/css-selector": "~2.8|~3.0",
-                "symfony/dependency-injection": "~3.3",
-                "symfony/dom-crawler": "~2.8|~3.0",
-                "symfony/expression-language": "~2.8|~3.0",
-                "symfony/finder": "~2.8|~3.0",
-                "symfony/process": "~2.8|~3.0",
-                "symfony/routing": "~2.8|~3.0",
-                "symfony/stopwatch": "~2.8|~3.0",
-                "symfony/templating": "~2.8|~3.0",
-                "symfony/translation": "~2.8|~3.0",
-                "symfony/var-dumper": "~3.3"
+                "symfony/browser-kit": "^4.3",
+                "symfony/config": "~3.4|~4.0",
+                "symfony/console": "~3.4|~4.0",
+                "symfony/css-selector": "~3.4|~4.0",
+                "symfony/dependency-injection": "^4.3",
+                "symfony/dom-crawler": "~3.4|~4.0",
+                "symfony/expression-language": "~3.4|~4.0",
+                "symfony/finder": "~3.4|~4.0",
+                "symfony/process": "~3.4|~4.0",
+                "symfony/routing": "~3.4|~4.0",
+                "symfony/stopwatch": "~3.4|~4.0",
+                "symfony/templating": "~3.4|~4.0",
+                "symfony/translation": "~4.2",
+                "symfony/translation-contracts": "^1.1",
+                "symfony/var-dumper": "^4.1.1",
+                "twig/twig": "^1.34|^2.4"
             },
             "suggest": {
                 "symfony/browser-kit": "",
-                "symfony/class-loader": "",
                 "symfony/config": "",
                 "symfony/console": "",
                 "symfony/dependency-injection": "",
-                "symfony/finder": "",
                 "symfony/var-dumper": ""
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony HttpKernel Component",
             "homepage": "https://symfony.com",
-            "time": "2017-08-01T10:25:59+00:00"
+            "time": "2019-08-26T16:47:42+00:00"
+        },
+        {
+            "name": "symfony/mime",
+            "version": "v4.3.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/mime.git",
+                "reference": "987a05df1c6ac259b34008b932551353f4f408df"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/mime/zipball/987a05df1c6ac259b34008b932551353f4f408df",
+                "reference": "987a05df1c6ac259b34008b932551353f4f408df",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "symfony/polyfill-intl-idn": "^1.10",
+                "symfony/polyfill-mbstring": "^1.0"
+            },
+            "require-dev": {
+                "egulias/email-validator": "^2.1.10",
+                "symfony/dependency-injection": "~3.4|^4.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Mime\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "A library to manipulate MIME messages",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "mime",
+                "mime-type"
+            ],
+            "time": "2019-08-22T08:16:11+00:00"
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.10.0",
+            "version": "v1.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
+                "reference": "550ebaac289296ce228a706d0867afc34687e3f4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
-                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
+                "reference": "550ebaac289296ce228a706d0867afc34687e3f4",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.9-dev"
+                    "dev-master": "1.12-dev"
                 }
             },
             "autoload": {
                 "MIT"
             ],
             "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "[email protected]"
+                },
                 {
                     "name": "Symfony Community",
                     "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "symfony/polyfill-iconv",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-iconv.git",
+                "reference": "685968b11e61a347c18bf25db32effa478be610f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/685968b11e61a347c18bf25db32effa478be610f",
+                "reference": "685968b11e61a347c18bf25db32effa478be610f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-iconv": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Iconv\\": ""
                 },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "name": "Gert de Pagter",
-                    "email": "[email protected]"
+                    "name": "Nicolas Grekas",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Iconv extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "iconv",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-idn",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-idn.git",
+                "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6af626ae6fa37d396dc90a399c0ff08e5cfc45b2",
+                "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "symfony/polyfill-mbstring": "^1.3",
+                "symfony/polyfill-php72": "^1.9"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Idn\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Laurent Bassin",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "idn",
+                "intl",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php72",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php72.git",
+                "reference": "04ce3335667451138df4307d6a9b61565560199e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e",
+                "reference": "04ce3335667451138df4307d6a9b61565560199e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php72\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill for ctype functions",
+            "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
             "homepage": "https://symfony.com",
             "keywords": [
                 "compatibility",
-                "ctype",
                 "polyfill",
-                "portable"
+                "portable",
+                "shim"
             ],
-            "time": "2018-08-06T14:22:27+00:00"
+            "time": "2019-08-06T08:03:45+00:00"
         },
         {
-            "name": "symfony/polyfill-mbstring",
-            "version": "v1.10.0",
+            "name": "symfony/polyfill-php73",
+            "version": "v1.12.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
+                "url": "https://github.com/symfony/polyfill-php73.git",
+                "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
-                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188",
+                "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.3"
             },
-            "suggest": {
-                "ext-mbstring": "For best performance"
-            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.9-dev"
+                    "dev-master": "1.12-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Mbstring\\": ""
+                    "Symfony\\Polyfill\\Php73\\": ""
                 },
                 "files": [
                     "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill for the Mbstring extension",
+            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
             "homepage": "https://symfony.com",
             "keywords": [
                 "compatibility",
-                "mbstring",
                 "polyfill",
                 "portable",
                 "shim"
             ],
-            "time": "2018-09-21T13:07:52+00:00"
+            "time": "2019-08-06T08:03:45+00:00"
         },
         {
             "name": "symfony/process",
-            "version": "v3.3.6",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a"
+                "reference": "e89969c00d762349f078db1128506f7f3dcc0d4a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a",
-                "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a",
+                "url": "https://api.github.com/repos/symfony/process/zipball/e89969c00d762349f078db1128506f7f3dcc0d4a",
+                "reference": "e89969c00d762349f078db1128506f7f3dcc0d4a",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^7.1.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Process Component",
             "homepage": "https://symfony.com",
-            "time": "2017-07-13T13:05:09+00:00"
+            "time": "2019-08-26T08:26:39+00:00"
         },
         {
             "name": "symfony/routing",
-            "version": "v3.3.6",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/routing.git",
-                "reference": "4aee1a917fd4859ff8b51b9fd1dfb790a5ecfa26"
+                "reference": "ff1049f6232dc5b6023b1ff1c6de56f82bcd264f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/routing/zipball/4aee1a917fd4859ff8b51b9fd1dfb790a5ecfa26",
-                "reference": "4aee1a917fd4859ff8b51b9fd1dfb790a5ecfa26",
+                "url": "https://api.github.com/repos/symfony/routing/zipball/ff1049f6232dc5b6023b1ff1c6de56f82bcd264f",
+                "reference": "ff1049f6232dc5b6023b1ff1c6de56f82bcd264f",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^7.1.3"
             },
             "conflict": {
-                "symfony/config": "<2.8",
-                "symfony/dependency-injection": "<3.3",
-                "symfony/yaml": "<3.3"
+                "symfony/config": "<4.2",
+                "symfony/dependency-injection": "<3.4",
+                "symfony/yaml": "<3.4"
             },
             "require-dev": {
-                "doctrine/annotations": "~1.0",
-                "doctrine/common": "~2.2",
+                "doctrine/annotations": "~1.2",
                 "psr/log": "~1.0",
-                "symfony/config": "~2.8|~3.0",
-                "symfony/dependency-injection": "~3.3",
-                "symfony/expression-language": "~2.8|~3.0",
-                "symfony/http-foundation": "~2.8|~3.0",
-                "symfony/yaml": "~3.3"
+                "symfony/config": "~4.2",
+                "symfony/dependency-injection": "~3.4|~4.0",
+                "symfony/expression-language": "~3.4|~4.0",
+                "symfony/http-foundation": "~3.4|~4.0",
+                "symfony/yaml": "~3.4|~4.0"
             },
             "suggest": {
                 "doctrine/annotations": "For using the annotation loader",
                 "symfony/config": "For using the all-in-one router or any loader",
-                "symfony/dependency-injection": "For loading routes from a service",
                 "symfony/expression-language": "For using expression matching",
                 "symfony/http-foundation": "For using a Symfony Request object",
                 "symfony/yaml": "For using the YAML loader"
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
                 "uri",
                 "url"
             ],
-            "time": "2017-07-21T17:43:13+00:00"
+            "time": "2019-08-26T08:26:39+00:00"
+        },
+        {
+            "name": "symfony/service-contracts",
+            "version": "v1.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/service-contracts.git",
+                "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ea7263d6b6d5f798b56a45a5b8d686725f2719a3",
+                "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "psr/container": "^1.0"
+            },
+            "suggest": {
+                "symfony/service-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Service\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to writing services",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "time": "2019-08-20T14:44:19+00:00"
         },
         {
             "name": "symfony/translation",
-            "version": "v3.3.6",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3"
+                "reference": "28498169dd334095fa981827992f3a24d50fed0f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3",
-                "reference": "35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/28498169dd334095fa981827992f3a24d50fed0f",
+                "reference": "28498169dd334095fa981827992f3a24d50fed0f",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
-                "symfony/polyfill-mbstring": "~1.0"
+                "php": "^7.1.3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/translation-contracts": "^1.1.6"
             },
             "conflict": {
-                "symfony/config": "<2.8",
-                "symfony/yaml": "<3.3"
+                "symfony/config": "<3.4",
+                "symfony/dependency-injection": "<3.4",
+                "symfony/yaml": "<3.4"
+            },
+            "provide": {
+                "symfony/translation-implementation": "1.0"
             },
             "require-dev": {
                 "psr/log": "~1.0",
-                "symfony/config": "~2.8|~3.0",
-                "symfony/intl": "^2.8.18|^3.2.5",
-                "symfony/yaml": "~3.3"
+                "symfony/config": "~3.4|~4.0",
+                "symfony/console": "~3.4|~4.0",
+                "symfony/dependency-injection": "~3.4|~4.0",
+                "symfony/finder": "~2.8|~3.0|~4.0",
+                "symfony/http-kernel": "~3.4|~4.0",
+                "symfony/intl": "~3.4|~4.0",
+                "symfony/service-contracts": "^1.1.2",
+                "symfony/var-dumper": "~3.4|~4.0",
+                "symfony/yaml": "~3.4|~4.0"
             },
             "suggest": {
-                "psr/log": "To use logging capability in translator",
+                "psr/log-implementation": "To use logging capability in translator",
                 "symfony/config": "",
                 "symfony/yaml": ""
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Translation Component",
             "homepage": "https://symfony.com",
-            "time": "2017-06-24T16:45:30+00:00"
+            "time": "2019-08-26T08:55:16+00:00"
+        },
+        {
+            "name": "symfony/translation-contracts",
+            "version": "v1.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/translation-contracts.git",
+                "reference": "325b17c24f3ee23cbecfa63ba809c6d89b5fa04a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/325b17c24f3ee23cbecfa63ba809c6d89b5fa04a",
+                "reference": "325b17c24f3ee23cbecfa63ba809c6d89b5fa04a",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3"
+            },
+            "suggest": {
+                "symfony/translation-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Translation\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to translation",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "time": "2019-08-02T12:15:04+00:00"
         },
         {
             "name": "symfony/var-dumper",
-            "version": "v3.3.6",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-dumper.git",
-                "reference": "b2623bccb969ad595c2090f9be498b74670d0663"
+                "reference": "641043e0f3e615990a0f29479f9c117e8a6698c6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b2623bccb969ad595c2090f9be498b74670d0663",
-                "reference": "b2623bccb969ad595c2090f9be498b74670d0663",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/641043e0f3e615990a0f29479f9c117e8a6698c6",
+                "reference": "641043e0f3e615990a0f29479f9c117e8a6698c6",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
-                "symfony/polyfill-mbstring": "~1.0"
+                "php": "^7.1.3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php72": "~1.5"
             },
             "conflict": {
-                "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
+                "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
+                "symfony/console": "<3.4"
             },
             "require-dev": {
                 "ext-iconv": "*",
+                "symfony/console": "~3.4|~4.0",
+                "symfony/process": "~3.4|~4.0",
                 "twig/twig": "~1.34|~2.4"
             },
             "suggest": {
                 "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
-                "ext-symfony_debug": ""
+                "ext-intl": "To show region name in time zone dump",
+                "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
             },
+            "bin": [
+                "Resources/bin/var-dump-server"
+            ],
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
                 "debug",
                 "dump"
             ],
-            "time": "2017-07-28T06:06:09+00:00"
+            "time": "2019-08-26T08:26:39+00:00"
         },
         {
             "name": "tijsverkoyen/css-to-inline-styles",
         },
         {
             "name": "vlucas/phpdotenv",
-            "version": "v2.5.2",
+            "version": "v3.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/vlucas/phpdotenv.git",
-                "reference": "cfd5dc225767ca154853752abc93aeec040fcf36"
+                "reference": "1bdf24f065975594f6a117f0f1f6cabf1333b156"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/cfd5dc225767ca154853752abc93aeec040fcf36",
-                "reference": "cfd5dc225767ca154853752abc93aeec040fcf36",
+                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1bdf24f065975594f6a117f0f1f6cabf1333b156",
+                "reference": "1bdf24f065975594f6a117f0f1f6cabf1333b156",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.9"
+                "php": "^5.4 || ^7.0",
+                "phpoption/phpoption": "^1.5",
+                "symfony/polyfill-ctype": "^1.9"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.0"
+                "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.5-dev"
+                    "dev-master": "3.6-dev"
                 }
             },
             "autoload": {
                 "BSD-3-Clause"
             ],
             "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "[email protected]",
+                    "homepage": "https://gjcampbell.co.uk/"
+                },
                 {
                     "name": "Vance Lucas",
                     "email": "[email protected]",
-                    "homepage": "http://www.vancelucas.com"
+                    "homepage": "https://vancelucas.com/"
                 }
             ],
             "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
                 "env",
                 "environment"
             ],
-            "time": "2018-10-30T17:29:25+00:00"
+            "time": "2019-09-10T21:37:39+00:00"
         }
     ],
     "packages-dev": [
         {
             "name": "barryvdh/laravel-debugbar",
-            "version": "v3.2.1",
+            "version": "v3.2.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/barryvdh/laravel-debugbar.git",
-                "reference": "9d5caf43c5f3a3aea2178942f281054805872e7c"
+                "reference": "18208d64897ab732f6c04a19b319fe8f1d57a9c0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/9d5caf43c5f3a3aea2178942f281054805872e7c",
-                "reference": "9d5caf43c5f3a3aea2178942f281054805872e7c",
+                "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/18208d64897ab732f6c04a19b319fe8f1d57a9c0",
+                "reference": "18208d64897ab732f6c04a19b319fe8f1d57a9c0",
                 "shasum": ""
             },
             "require": {
-                "illuminate/routing": "5.5.x|5.6.x|5.7.x",
-                "illuminate/session": "5.5.x|5.6.x|5.7.x",
-                "illuminate/support": "5.5.x|5.6.x|5.7.x",
+                "illuminate/routing": "^5.5|^6",
+                "illuminate/session": "^5.5|^6",
+                "illuminate/support": "^5.5|^6",
                 "maximebf/debugbar": "~1.15.0",
                 "php": ">=7.0",
                 "symfony/debug": "^3|^4",
                 "profiler",
                 "webprofiler"
             ],
-            "time": "2018-11-09T08:37:55+00:00"
+            "time": "2019-08-29T07:01:03+00:00"
         },
         {
             "name": "barryvdh/laravel-ide-helper",
-            "version": "v2.5.3",
+            "version": "v2.6.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/barryvdh/laravel-ide-helper.git",
-                "reference": "3d7f1240896a075aa23b13f82dfcbe165dadeef2"
+                "reference": "8740a9a158d3dd5cfc706a9d4cc1bf7a518f99f3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/3d7f1240896a075aa23b13f82dfcbe165dadeef2",
-                "reference": "3d7f1240896a075aa23b13f82dfcbe165dadeef2",
+                "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/8740a9a158d3dd5cfc706a9d4cc1bf7a518f99f3",
+                "reference": "8740a9a158d3dd5cfc706a9d4cc1bf7a518f99f3",
                 "shasum": ""
             },
             "require": {
                 "barryvdh/reflection-docblock": "^2.0.6",
                 "composer/composer": "^1.6",
-                "illuminate/console": "^5.5,<5.8",
-                "illuminate/filesystem": "^5.5,<5.8",
-                "illuminate/support": "^5.5,<5.8",
+                "doctrine/dbal": "~2.3",
+                "illuminate/console": "^5.5|^6",
+                "illuminate/filesystem": "^5.5|^6",
+                "illuminate/support": "^5.5|^6",
                 "php": ">=7"
             },
             "require-dev": {
-                "doctrine/dbal": "~2.3",
-                "illuminate/config": "^5.1,<5.8",
-                "illuminate/view": "^5.1,<5.8",
+                "illuminate/config": "^5.5|^6",
+                "illuminate/view": "^5.5|^6",
                 "phpro/grumphp": "^0.14",
                 "phpunit/phpunit": "4.*",
                 "scrutinizer/ocular": "~1.1",
                 "squizlabs/php_codesniffer": "^3"
             },
-            "suggest": {
-                "doctrine/dbal": "Load information from the database about models for phpdocs (~2.3)"
-            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.5-dev"
+                    "dev-master": "2.6-dev"
                 },
                 "laravel": {
                     "providers": [
                 "phpstorm",
                 "sublime"
             ],
-            "time": "2018-12-19T12:12:05+00:00"
+            "time": "2019-09-08T09:56:38+00:00"
         },
         {
             "name": "barryvdh/reflection-docblock",
         },
         {
             "name": "composer/ca-bundle",
-            "version": "1.1.3",
+            "version": "1.2.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/ca-bundle.git",
-                "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660"
+                "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660",
-                "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660",
+                "url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527",
+                "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527",
                 "shasum": ""
             },
             "require": {
                 "ext-openssl": "*",
                 "ext-pcre": "*",
-                "php": "^5.3.2 || ^7.0"
+                "php": "^5.3.2 || ^7.0 || ^8.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8",
                 "psr/log": "^1.0",
                 "symfony/process": "^2.5 || ^3.0 || ^4.0"
             },
                 "ssl",
                 "tls"
             ],
-            "time": "2018-10-18T06:09:13+00:00"
+            "time": "2019-08-30T08:44:50+00:00"
         },
         {
             "name": "composer/composer",
-            "version": "1.8.0",
+            "version": "1.9.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/composer.git",
-                "reference": "d8aef3af866b28786ce9b8647e52c42496436669"
+                "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/composer/zipball/d8aef3af866b28786ce9b8647e52c42496436669",
-                "reference": "d8aef3af866b28786ce9b8647e52c42496436669",
+                "url": "https://api.github.com/repos/composer/composer/zipball/314aa57fdcfc942065996f59fb73a8b3f74f3fa5",
+                "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.8-dev"
+                    "dev-master": "1.9-dev"
                 }
             },
             "autoload": {
                     "homepage": "http://seld.be"
                 }
             ],
-            "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.",
+            "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.",
             "homepage": "https://getcomposer.org/",
             "keywords": [
                 "autoload",
                 "dependency",
                 "package"
             ],
-            "time": "2018-12-03T09:31:16+00:00"
+            "time": "2019-08-02T18:55:33+00:00"
         },
         {
             "name": "composer/semver",
-            "version": "1.4.2",
+            "version": "1.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/semver.git",
-                "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573"
+                "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573",
-                "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573",
+                "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e",
+                "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e",
                 "shasum": ""
             },
             "require": {
                     "homepage": "http://robbast.nl"
                 }
             ],
-            "description": "Semver library that offers utilities, version constraint parsing and validation.",
+            "description": "Semver library that offers utilities, version constraint parsing and validation.",
+            "keywords": [
+                "semantic",
+                "semver",
+                "validation",
+                "versioning"
+            ],
+            "time": "2019-03-19T17:25:45+00:00"
+        },
+        {
+            "name": "composer/spdx-licenses",
+            "version": "1.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/composer/spdx-licenses.git",
+                "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7ac1e6aec371357df067f8a688c3d6974df68fa5",
+                "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.2 || ^7.0 || ^8.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Composer\\Spdx\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nils Adermann",
+                    "email": "[email protected]",
+                    "homepage": "http://www.naderman.de"
+                },
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "[email protected]",
+                    "homepage": "http://seld.be"
+                },
+                {
+                    "name": "Rob Bast",
+                    "email": "[email protected]",
+                    "homepage": "http://robbast.nl"
+                }
+            ],
+            "description": "SPDX licenses list and validation library.",
+            "keywords": [
+                "license",
+                "spdx",
+                "validator"
+            ],
+            "time": "2019-07-29T10:31:59+00:00"
+        },
+        {
+            "name": "composer/xdebug-handler",
+            "version": "1.3.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/composer/xdebug-handler.git",
+                "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f",
+                "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.2 || ^7.0",
+                "psr/log": "^1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Composer\\XdebugHandler\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "John Stevenson",
+                    "email": "[email protected]"
+                }
+            ],
+            "description": "Restarts a process without xdebug.",
+            "keywords": [
+                "Xdebug",
+                "performance"
+            ],
+            "time": "2019-05-27T17:52:04+00:00"
+        },
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "a2c590166b2133a4633738648b6b064edae0814a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a",
+                "reference": "a2c590166b2133a4633738648b6b064edae0814a",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^6.0",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpbench/phpbench": "^0.13",
+                "phpstan/phpstan-phpunit": "^0.11",
+                "phpstan/phpstan-shim": "^0.11",
+                "phpunit/phpunit": "^7.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "[email protected]",
+                    "homepage": "http://ocramius.github.com/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
             "keywords": [
-                "semantic",
-                "semver",
-                "validation",
-                "versioning"
+                "constructor",
+                "instantiate"
             ],
-            "time": "2016-08-30T16:08:34+00:00"
+            "time": "2019-03-17T17:37:11+00:00"
         },
         {
-            "name": "composer/spdx-licenses",
-            "version": "1.5.0",
+            "name": "facade/flare-client-php",
+            "version": "1.0.4",
             "source": {
                 "type": "git",
-                "url": "https://github.com/composer/spdx-licenses.git",
-                "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2"
+                "url": "https://github.com/facade/flare-client-php.git",
+                "reference": "7128b251b48f24ef64e5cddd7f8d40cc3a06fd3e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7a9556b22bd9d4df7cad89876b00af58ef20d3a2",
-                "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2",
+                "url": "https://api.github.com/repos/facade/flare-client-php/zipball/7128b251b48f24ef64e5cddd7f8d40cc3a06fd3e",
+                "reference": "7128b251b48f24ef64e5cddd7f8d40cc3a06fd3e",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.3.2 || ^7.0"
+                "facade/ignition-contracts": "~1.0",
+                "illuminate/pipeline": "~5.5|~5.6|~5.7|~5.8|^6.0",
+                "php": "^7.1",
+                "symfony/http-foundation": "~3.3|~4.1",
+                "symfony/var-dumper": "^3.4|^4.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
-                "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
+                "larapack/dd": "^1.1",
+                "phpunit/phpunit": "^7.0",
+                "spatie/phpunit-snapshot-assertions": "^2.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.x-dev"
+                    "dev-master": "1.0-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Composer\\Spdx\\": "src"
-                }
+                    "Facade\\FlareClient\\": "src"
+                },
+                "files": [
+                    "src/helpers.php"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "MIT"
             ],
-            "authors": [
-                {
-                    "name": "Nils Adermann",
-                    "email": "[email protected]",
-                    "homepage": "http://www.naderman.de"
-                },
-                {
-                    "name": "Jordi Boggiano",
-                    "email": "[email protected]",
-                    "homepage": "http://seld.be"
-                },
-                {
-                    "name": "Rob Bast",
-                    "email": "[email protected]",
-                    "homepage": "http://robbast.nl"
-                }
-            ],
-            "description": "SPDX licenses list and validation library.",
+            "description": "Send PHP errors to Flare",
+            "homepage": "https://github.com/facade/flare-client-php",
             "keywords": [
-                "license",
-                "spdx",
-                "validator"
+                "exception",
+                "facade",
+                "flare",
+                "reporting"
             ],
-            "time": "2018-11-01T09:45:54+00:00"
+            "time": "2019-09-11T14:19:56+00:00"
         },
         {
-            "name": "composer/xdebug-handler",
-            "version": "1.3.1",
+            "name": "facade/ignition",
+            "version": "1.6.5",
             "source": {
                 "type": "git",
-                "url": "https://github.com/composer/xdebug-handler.git",
-                "reference": "dc523135366eb68f22268d069ea7749486458562"
+                "url": "https://github.com/facade/ignition.git",
+                "reference": "97244f6d511332f3574acab8242c09ddcfda892b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/dc523135366eb68f22268d069ea7749486458562",
-                "reference": "dc523135366eb68f22268d069ea7749486458562",
+                "url": "https://api.github.com/repos/facade/ignition/zipball/97244f6d511332f3574acab8242c09ddcfda892b",
+                "reference": "97244f6d511332f3574acab8242c09ddcfda892b",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.3.2 || ^7.0",
-                "psr/log": "^1.0"
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "facade/flare-client-php": "^1.0.4",
+                "facade/ignition-contracts": "^1.0",
+                "filp/whoops": "^2.4",
+                "illuminate/support": "~5.5.0 || ~5.6.0 || ~5.7.0 || ~5.8.0 || ^6.0",
+                "monolog/monolog": "^1.12 || ^2.0",
+                "php": "^7.1",
+                "scrivo/highlight.php": "^9.15",
+                "symfony/console": "^3.4 || ^4.0",
+                "symfony/var-dumper": "^3.4 || ^4.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5"
+                "friendsofphp/php-cs-fixer": "^2.14",
+                "mockery/mockery": "^1.2",
+                "orchestra/testbench": "^3.5 || ^3.6 || ^3.7 || ^3.8 || ^4.0"
+            },
+            "suggest": {
+                "laravel/telescope": "^2.0"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                },
+                "laravel": {
+                    "providers": [
+                        "Facade\\Ignition\\IgnitionServiceProvider"
+                    ],
+                    "aliases": {
+                        "Flare": "Facade\\Ignition\\Facades\\Flare"
+                    }
+                }
+            },
             "autoload": {
                 "psr-4": {
-                    "Composer\\XdebugHandler\\": "src"
+                    "Facade\\Ignition\\": "src"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "MIT"
             ],
-            "authors": [
-                {
-                    "name": "John Stevenson",
-                    "email": "[email protected]"
-                }
-            ],
-            "description": "Restarts a process without xdebug.",
+            "description": "A beautiful error page for Laravel applications.",
+            "homepage": "https://github.com/facade/ignition",
             "keywords": [
-                "Xdebug",
-                "performance"
+                "error",
+                "flare",
+                "laravel",
+                "page"
             ],
-            "time": "2018-11-29T10:59:02+00:00"
+            "time": "2019-09-13T13:38:04+00:00"
         },
         {
-            "name": "doctrine/instantiator",
-            "version": "1.0.5",
+            "name": "facade/ignition-contracts",
+            "version": "1.0.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/doctrine/instantiator.git",
-                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+                "url": "https://github.com/facade/ignition-contracts.git",
+                "reference": "f445db0fb86f48e205787b2592840dd9c80ded28"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
-                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "url": "https://api.github.com/repos/facade/ignition-contracts/zipball/f445db0fb86f48e205787b2592840dd9c80ded28",
+                "reference": "f445db0fb86f48e205787b2592840dd9c80ded28",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3,<8.0-DEV"
-            },
-            "require-dev": {
-                "athletic/athletic": "~0.1.8",
-                "ext-pdo": "*",
-                "ext-phar": "*",
-                "phpunit/phpunit": "~4.0",
-                "squizlabs/php_codesniffer": "~2.0"
+                "php": "^7.1"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0.x-dev"
-                }
-            },
             "autoload": {
                 "psr-4": {
-                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                    "Facade\\IgnitionContracts\\": "src"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             ],
             "authors": [
                 {
-                    "name": "Marco Pivetta",
-                    "email": "[email protected]",
-                    "homepage": "http://ocramius.github.com/"
+                    "name": "Freek Van der Herten",
+                    "email": "[email protected]",
+                    "homepage": "https://flareapp.io",
+                    "role": "Developer"
                 }
             ],
-            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
-            "homepage": "https://github.com/doctrine/instantiator",
+            "description": "Solution contracts for Ignition",
+            "homepage": "https://github.com/facade/ignition-contracts",
             "keywords": [
-                "constructor",
-                "instantiate"
+                "contracts",
+                "flare",
+                "ignition"
             ],
-            "time": "2015-06-14T21:17:01+00:00"
+            "time": "2019-08-30T14:06:08+00:00"
         },
         {
             "name": "filp/whoops",
-            "version": "2.3.1",
+            "version": "2.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/filp/whoops.git",
-                "reference": "bc0fd11bc455cc20ee4b5edabc63ebbf859324c7"
+                "reference": "cde50e6720a39fdacb240159d3eea6865d51fd96"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/filp/whoops/zipball/bc0fd11bc455cc20ee4b5edabc63ebbf859324c7",
-                "reference": "bc0fd11bc455cc20ee4b5edabc63ebbf859324c7",
+                "url": "https://api.github.com/repos/filp/whoops/zipball/cde50e6720a39fdacb240159d3eea6865d51fd96",
+                "reference": "cde50e6720a39fdacb240159d3eea6865d51fd96",
                 "shasum": ""
             },
             "require": {
             "authors": [
                 {
                     "name": "Filipe Dobreira",
-                    "homepage": "https://github.com/filp",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "homepage": "https://github.com/filp"
                 }
             ],
             "description": "php error handling for cool kids",
                 "throwable",
                 "whoops"
             ],
-            "time": "2018-10-23T09:00:00+00:00"
+            "time": "2019-08-07T09:00:00+00:00"
         },
         {
             "name": "fzaninotto/faker",
             ],
             "time": "2016-01-20T08:20:44+00:00"
         },
+        {
+            "name": "jakub-onderka/php-console-color",
+            "version": "v0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/JakubOnderka/PHP-Console-Color.git",
+                "reference": "d5deaecff52a0d61ccb613bb3804088da0307191"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191",
+                "reference": "d5deaecff52a0d61ccb613bb3804088da0307191",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "jakub-onderka/php-code-style": "1.0",
+                "jakub-onderka/php-parallel-lint": "1.0",
+                "jakub-onderka/php-var-dump-check": "0.*",
+                "phpunit/phpunit": "~4.3",
+                "squizlabs/php_codesniffer": "1.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "JakubOnderka\\PhpConsoleColor\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-2-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jakub Onderka",
+                    "email": "[email protected]"
+                }
+            ],
+            "time": "2018-09-29T17:23:10+00:00"
+        },
+        {
+            "name": "jakub-onderka/php-console-highlighter",
+            "version": "v0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git",
+                "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/9f7a229a69d52506914b4bc61bfdb199d90c5547",
+                "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "jakub-onderka/php-console-color": "~0.2",
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "jakub-onderka/php-code-style": "~1.0",
+                "jakub-onderka/php-parallel-lint": "~1.0",
+                "jakub-onderka/php-var-dump-check": "~0.1",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "~1.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "JakubOnderka\\PhpConsoleHighlighter\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jakub Onderka",
+                    "email": "[email protected]",
+                    "homepage": "http://www.acci.cz/"
+                }
+            ],
+            "description": "Highlight PHP code in terminal",
+            "time": "2018-09-29T18:48:56+00:00"
+        },
         {
             "name": "justinrainbow/json-schema",
             "version": "5.2.8",
         },
         {
             "name": "laravel/browser-kit-testing",
-            "version": "v2.0.1",
+            "version": "v5.1.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/browser-kit-testing.git",
-                "reference": "f0bb9f200ec35f9d876ded6eacfbc60868d311b9"
+                "reference": "cb0cf22cf38fe8796842adc8b9ad550ded2a1377"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/browser-kit-testing/zipball/f0bb9f200ec35f9d876ded6eacfbc60868d311b9",
-                "reference": "f0bb9f200ec35f9d876ded6eacfbc60868d311b9",
+                "url": "https://api.github.com/repos/laravel/browser-kit-testing/zipball/cb0cf22cf38fe8796842adc8b9ad550ded2a1377",
+                "reference": "cb0cf22cf38fe8796842adc8b9ad550ded2a1377",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
-                "phpunit/phpunit": "~6.0",
-                "symfony/css-selector": "~3.1",
-                "symfony/dom-crawler": "~3.1"
+                "ext-dom": "*",
+                "ext-json": "*",
+                "illuminate/contracts": "~5.7.0|~5.8.0|^6.0",
+                "illuminate/database": "~5.7.0|~5.8.0|^6.0",
+                "illuminate/http": "~5.7.0|~5.8.0|^6.0",
+                "illuminate/support": "~5.7.0|~5.8.0|^6.0",
+                "mockery/mockery": "^1.0",
+                "php": ">=7.1.3",
+                "phpunit/phpunit": "^7.0|^8.0",
+                "symfony/console": "^4.2",
+                "symfony/css-selector": "^4.2",
+                "symfony/dom-crawler": "^4.2",
+                "symfony/http-foundation": "^4.2",
+                "symfony/http-kernel": "^4.2"
+            },
+            "require-dev": {
+                "laravel/framework": "~5.7.0|~5.8.0|^6.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0-dev"
+                    "dev-master": "5.0-dev"
                 }
             },
             "autoload": {
                     "email": "[email protected]"
                 }
             ],
-            "description": "Provides backwards compatibility for BrowserKit testing in Laravel 5.4.",
+            "description": "Provides backwards compatibility for BrowserKit testing in the latest Laravel release.",
             "keywords": [
                 "laravel",
                 "testing"
             ],
-            "time": "2017-06-21T11:44:53+00:00"
+            "time": "2019-07-30T14:57:44+00:00"
         },
         {
             "name": "maximebf/debugbar",
         },
         {
             "name": "mockery/mockery",
-            "version": "1.2.0",
+            "version": "1.2.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/mockery/mockery.git",
-                "reference": "100633629bf76d57430b86b7098cd6beb996a35a"
+                "reference": "4eff936d83eb809bde2c57a3cea0ee9643769031"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/mockery/mockery/zipball/100633629bf76d57430b86b7098cd6beb996a35a",
-                "reference": "100633629bf76d57430b86b7098cd6beb996a35a",
+                "url": "https://api.github.com/repos/mockery/mockery/zipball/4eff936d83eb809bde2c57a3cea0ee9643769031",
+                "reference": "4eff936d83eb809bde2c57a3cea0ee9643769031",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.6.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "~5.7.10|~6.5|~7.0"
+                "phpunit/phpunit": "~5.7.10|~6.5|~7.0|~8.0"
             },
             "type": "library",
             "extra": {
                 "test double",
                 "testing"
             ],
-            "time": "2018-10-02T21:52:37+00:00"
+            "time": "2019-08-07T15:01:07+00:00"
         },
         {
             "name": "myclabs/deep-copy",
-            "version": "1.7.0",
+            "version": "1.9.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/myclabs/DeepCopy.git",
-                "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e"
+                "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
-                "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea",
+                "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.6 || ^7.0"
+                "php": "^7.1"
+            },
+            "replace": {
+                "myclabs/deep-copy": "self.version"
             },
             "require-dev": {
                 "doctrine/collections": "^1.0",
                 "doctrine/common": "^2.6",
-                "phpunit/phpunit": "^4.1"
+                "phpunit/phpunit": "^7.1"
             },
             "type": "library",
             "autoload": {
                 "object",
                 "object graph"
             ],
-            "time": "2017-10-19T19:58:43+00:00"
+            "time": "2019-08-09T12:45:53+00:00"
+        },
+        {
+            "name": "nunomaduro/collision",
+            "version": "v3.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nunomaduro/collision.git",
+                "reference": "af42d339fe2742295a54f6fdd42aaa6f8c4aca68"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nunomaduro/collision/zipball/af42d339fe2742295a54f6fdd42aaa6f8c4aca68",
+                "reference": "af42d339fe2742295a54f6fdd42aaa6f8c4aca68",
+                "shasum": ""
+            },
+            "require": {
+                "filp/whoops": "^2.1.4",
+                "jakub-onderka/php-console-highlighter": "0.3.*|0.4.*",
+                "php": "^7.1",
+                "symfony/console": "~2.8|~3.3|~4.0"
+            },
+            "require-dev": {
+                "laravel/framework": "5.8.*",
+                "nunomaduro/larastan": "^0.3.0",
+                "phpstan/phpstan": "^0.11",
+                "phpunit/phpunit": "~8.0"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "NunoMaduro\\Collision\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nuno Maduro",
+                    "email": "[email protected]"
+                }
+            ],
+            "description": "Cli error handling for console/command-line PHP applications.",
+            "keywords": [
+                "artisan",
+                "cli",
+                "command-line",
+                "console",
+                "error",
+                "handling",
+                "laravel",
+                "laravel-zero",
+                "php",
+                "symfony"
+            ],
+            "time": "2019-03-07T21:35:13+00:00"
         },
         {
             "name": "phar-io/manifest",
-            "version": "1.0.1",
+            "version": "1.0.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phar-io/manifest.git",
-                "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0"
+                "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0",
-                "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0",
+                "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
+                "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
                 "shasum": ""
             },
             "require": {
                 "ext-dom": "*",
                 "ext-phar": "*",
-                "phar-io/version": "^1.0.1",
+                "phar-io/version": "^2.0",
                 "php": "^5.6 || ^7.0"
             },
             "type": "library",
             "authors": [
                 {
                     "name": "Arne Blankerts",
-                    "email": "[email protected]",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "email": "[email protected]"
                 },
                 {
                     "name": "Sebastian Heuer",
-                    "email": "[email protected]",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "email": "[email protected]"
                 },
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "[email protected]",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "email": "[email protected]"
                 }
             ],
             "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
-            "time": "2017-03-05T18:14:27+00:00"
+            "time": "2018-07-08T19:23:20+00:00"
         },
         {
             "name": "phar-io/version",
-            "version": "1.0.1",
+            "version": "2.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phar-io/version.git",
-                "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df"
+                "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df",
-                "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df",
+                "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6",
+                "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6",
                 "shasum": ""
             },
             "require": {
                 }
             ],
             "description": "Library for handling version information and constraints",
-            "time": "2017-03-05T17:38:23+00:00"
+            "time": "2018-07-08T19:19:57+00:00"
         },
         {
             "name": "phpdocumentor/reflection-common",
-            "version": "1.0.1",
+            "version": "2.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
-                "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
+                "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
-                "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a",
+                "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5"
+                "php": ">=7.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.6"
+                "phpunit/phpunit": "~6"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "2.x-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "phpDocumentor\\Reflection\\": [
-                        "src"
-                    ]
+                    "phpDocumentor\\Reflection\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 "reflection",
                 "static analysis"
             ],
-            "time": "2017-09-11T18:02:19+00:00"
+            "time": "2018-08-07T13:53:10+00:00"
         },
         {
             "name": "phpdocumentor/reflection-docblock",
-            "version": "4.3.0",
+            "version": "4.3.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
-                "reference": "94fd0001232e47129dd3504189fa1c7225010d08"
+                "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08",
-                "reference": "94fd0001232e47129dd3504189fa1c7225010d08",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e",
+                "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e",
                 "shasum": ""
             },
             "require": {
                 "php": "^7.0",
-                "phpdocumentor/reflection-common": "^1.0.0",
-                "phpdocumentor/type-resolver": "^0.4.0",
+                "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0",
+                "phpdocumentor/type-resolver": "~0.4 || ^1.0.0",
                 "webmozart/assert": "^1.0"
             },
             "require-dev": {
-                "doctrine/instantiator": "~1.0.5",
+                "doctrine/instantiator": "^1.0.5",
                 "mockery/mockery": "^1.0",
                 "phpunit/phpunit": "^6.4"
             },
                 }
             ],
             "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
-            "time": "2017-11-30T07:14:17+00:00"
+            "time": "2019-09-12T14:27:41+00:00"
         },
         {
             "name": "phpdocumentor/type-resolver",
-            "version": "0.4.0",
+            "version": "1.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/TypeResolver.git",
-                "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
+                "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
-                "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
+                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
+                "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5 || ^7.0",
-                "phpdocumentor/reflection-common": "^1.0"
+                "php": "^7.1",
+                "phpdocumentor/reflection-common": "^2.0"
             },
             "require-dev": {
-                "mockery/mockery": "^0.9.4",
-                "phpunit/phpunit": "^5.2||^4.8.24"
+                "ext-tokenizer": "^7.1",
+                "mockery/mockery": "~1",
+                "phpunit/phpunit": "^7.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "1.x-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "phpDocumentor\\Reflection\\": [
-                        "src/"
-                    ]
+                    "phpDocumentor\\Reflection\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "[email protected]"
+                }
+            ],
+            "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+            "time": "2019-08-22T18:11:29+00:00"
+        },
+        {
+            "name": "phploc/phploc",
+            "version": "5.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phploc.git",
+                "reference": "5b714ccb7cb8ca29ccf9caf6eb1aed0131d3a884"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phploc/zipball/5b714ccb7cb8ca29ccf9caf6eb1aed0131d3a884",
+                "reference": "5b714ccb7cb8ca29ccf9caf6eb1aed0131d3a884",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2",
+                "sebastian/finder-facade": "^1.1",
+                "sebastian/version": "^2.0",
+                "symfony/console": "^4.0"
+            },
+            "bin": [
+                "phploc"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.0-dev"
                 }
             },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "MIT"
+                "BSD-3-Clause"
             ],
             "authors": [
                 {
-                    "name": "Mike van Riel",
-                    "email": "[email protected]"
+                    "name": "Sebastian Bergmann",
+                    "email": "[email protected]",
+                    "role": "lead"
                 }
             ],
-            "time": "2017-07-14T14:27:02+00:00"
+            "description": "A tool for quickly measuring the size of a PHP project.",
+            "homepage": "https://github.com/sebastianbergmann/phploc",
+            "time": "2019-03-16T10:41:19+00:00"
         },
         {
             "name": "phpspec/prophecy",
-            "version": "1.8.0",
+            "version": "1.8.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06"
+                "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
-                "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
+                "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
                 "shasum": ""
             },
             "require": {
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Prophecy\\": "src/"
+                "psr-4": {
+                    "Prophecy\\": "src/Prophecy"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 "spy",
                 "stub"
             ],
-            "time": "2018-08-05T17:53:17+00:00"
+            "time": "2019-06-13T12:50:23+00:00"
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "5.3.2",
+            "version": "7.0.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "c89677919c5dd6d3b3852f230a663118762218ac"
+                "reference": "7743bbcfff2a907e9ee4a25be13d0f8ec5e73800"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac",
-                "reference": "c89677919c5dd6d3b3852f230a663118762218ac",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7743bbcfff2a907e9ee4a25be13d0f8ec5e73800",
+                "reference": "7743bbcfff2a907e9ee4a25be13d0f8ec5e73800",
                 "shasum": ""
             },
             "require": {
                 "ext-dom": "*",
                 "ext-xmlwriter": "*",
-                "php": "^7.0",
-                "phpunit/php-file-iterator": "^1.4.2",
+                "php": "^7.2",
+                "phpunit/php-file-iterator": "^2.0.2",
                 "phpunit/php-text-template": "^1.2.1",
-                "phpunit/php-token-stream": "^2.0.1",
+                "phpunit/php-token-stream": "^3.1.0",
                 "sebastian/code-unit-reverse-lookup": "^1.0.1",
-                "sebastian/environment": "^3.0",
+                "sebastian/environment": "^4.2.2",
                 "sebastian/version": "^2.0.1",
-                "theseer/tokenizer": "^1.1"
+                "theseer/tokenizer": "^1.1.3"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.0"
+                "phpunit/phpunit": "^8.2.2"
             },
             "suggest": {
-                "ext-xdebug": "^2.5.5"
+                "ext-xdebug": "^2.7.2"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "5.3.x-dev"
+                    "dev-master": "7.0-dev"
                 }
             },
             "autoload": {
                 "testing",
                 "xunit"
             ],
-            "time": "2018-04-06T15:36:58+00:00"
+            "time": "2019-07-25T05:31:54+00:00"
         },
         {
             "name": "phpunit/php-file-iterator",
-            "version": "1.4.5",
+            "version": "2.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
-                "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
+                "reference": "050bedf145a257b1ff02746c31894800e5122946"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
-                "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946",
+                "reference": "050bedf145a257b1ff02746c31894800e5122946",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.3"
+                "php": "^7.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7.1"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.4.x-dev"
+                    "dev-master": "2.0.x-dev"
                 }
             },
             "autoload": {
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sb@sebastian-bergmann.de",
+                    "email": "sebastian@phpunit.de",
                     "role": "lead"
                 }
             ],
                 "filesystem",
                 "iterator"
             ],
-            "time": "2017-11-27T13:52:08+00:00"
+            "time": "2018-09-13T20:33:42+00:00"
         },
         {
             "name": "phpunit/php-text-template",
         },
         {
             "name": "phpunit/php-timer",
-            "version": "1.0.9",
+            "version": "2.1.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-timer.git",
-                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
+                "reference": "1038454804406b0b5f5f520358e78c1c2f71501e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
-                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e",
+                "reference": "1038454804406b0b5f5f520358e78c1c2f71501e",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.3.3 || ^7.0"
+                "php": "^7.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+                "phpunit/phpunit": "^7.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0-dev"
+                    "dev-master": "2.1-dev"
                 }
             },
             "autoload": {
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sb@sebastian-bergmann.de",
+                    "email": "sebastian@phpunit.de",
                     "role": "lead"
                 }
             ],
             "keywords": [
                 "timer"
             ],
-            "time": "2017-02-26T11:10:40+00:00"
+            "time": "2019-06-07T04:22:29+00:00"
         },
         {
             "name": "phpunit/php-token-stream",
-            "version": "2.0.2",
+            "version": "3.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-token-stream.git",
-                "reference": "791198a2c6254db10131eecfe8c06670700904db"
+                "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db",
-                "reference": "791198a2c6254db10131eecfe8c06670700904db",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e899757bb3df5ff6e95089132f32cd59aac2220a",
+                "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a",
                 "shasum": ""
             },
             "require": {
                 "ext-tokenizer": "*",
-                "php": "^7.0"
+                "php": "^7.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.2.4"
+                "phpunit/phpunit": "^7.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0-dev"
+                    "dev-master": "3.1-dev"
                 }
             },
             "autoload": {
             "keywords": [
                 "tokenizer"
             ],
-            "time": "2017-11-27T05:48:46+00:00"
+            "time": "2019-07-25T05:29:42+00:00"
         },
         {
             "name": "phpunit/phpunit",
-            "version": "6.5.13",
+            "version": "8.3.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "0973426fb012359b2f18d3bd1e90ef1172839693"
+                "reference": "302faed7059fde575cf3403a78c730c5e3a62750"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0973426fb012359b2f18d3bd1e90ef1172839693",
-                "reference": "0973426fb012359b2f18d3bd1e90ef1172839693",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/302faed7059fde575cf3403a78c730c5e3a62750",
+                "reference": "302faed7059fde575cf3403a78c730c5e3a62750",
                 "shasum": ""
             },
             "require": {
+                "doctrine/instantiator": "^1.2.0",
                 "ext-dom": "*",
                 "ext-json": "*",
                 "ext-libxml": "*",
                 "ext-mbstring": "*",
                 "ext-xml": "*",
-                "myclabs/deep-copy": "^1.6.1",
-                "phar-io/manifest": "^1.0.1",
-                "phar-io/version": "^1.0",
-                "php": "^7.0",
-                "phpspec/prophecy": "^1.7",
-                "phpunit/php-code-coverage": "^5.3",
-                "phpunit/php-file-iterator": "^1.4.3",
+                "ext-xmlwriter": "*",
+                "myclabs/deep-copy": "^1.9.1",
+                "phar-io/manifest": "^1.0.3",
+                "phar-io/version": "^2.0.1",
+                "php": "^7.2",
+                "phpspec/prophecy": "^1.8.1",
+                "phpunit/php-code-coverage": "^7.0.7",
+                "phpunit/php-file-iterator": "^2.0.2",
                 "phpunit/php-text-template": "^1.2.1",
-                "phpunit/php-timer": "^1.0.9",
-                "phpunit/phpunit-mock-objects": "^5.0.9",
-                "sebastian/comparator": "^2.1",
-                "sebastian/diff": "^2.0",
-                "sebastian/environment": "^3.1",
-                "sebastian/exporter": "^3.1",
-                "sebastian/global-state": "^2.0",
+                "phpunit/php-timer": "^2.1.2",
+                "sebastian/comparator": "^3.0.2",
+                "sebastian/diff": "^3.0.2",
+                "sebastian/environment": "^4.2.2",
+                "sebastian/exporter": "^3.1.1",
+                "sebastian/global-state": "^3.0.0",
                 "sebastian/object-enumerator": "^3.0.3",
-                "sebastian/resource-operations": "^1.0",
+                "sebastian/resource-operations": "^2.0.1",
+                "sebastian/type": "^1.1.3",
                 "sebastian/version": "^2.0.1"
             },
-            "conflict": {
-                "phpdocumentor/reflection-docblock": "3.0.2",
-                "phpunit/dbunit": "<3.0"
-            },
             "require-dev": {
                 "ext-pdo": "*"
             },
             "suggest": {
+                "ext-soap": "*",
                 "ext-xdebug": "*",
-                "phpunit/php-invoker": "^1.1"
+                "phpunit/php-invoker": "^2.0.0"
             },
             "bin": [
                 "phpunit"
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "6.5.x-dev"
+                    "dev-master": "8.3-dev"
                 }
             },
             "autoload": {
                 "testing",
                 "xunit"
             ],
-            "time": "2018-09-08T15:10:43+00:00"
+            "time": "2019-09-14T09:12:03+00:00"
         },
         {
-            "name": "phpunit/phpunit-mock-objects",
-            "version": "5.0.10",
+            "name": "scrivo/highlight.php",
+            "version": "v9.15.10.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
-                "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f"
+                "url": "https://github.com/scrivo/highlight.php.git",
+                "reference": "9ad3adb4456dc91196327498dbbce6aa1ba1239e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f",
-                "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f",
+                "url": "https://api.github.com/repos/scrivo/highlight.php/zipball/9ad3adb4456dc91196327498dbbce6aa1ba1239e",
+                "reference": "9ad3adb4456dc91196327498dbbce6aa1ba1239e",
                 "shasum": ""
             },
             "require": {
-                "doctrine/instantiator": "^1.0.5",
-                "php": "^7.0",
-                "phpunit/php-text-template": "^1.2.1",
-                "sebastian/exporter": "^3.1"
-            },
-            "conflict": {
-                "phpunit/phpunit": "<6.0"
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "php": ">=5.4"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.5.11"
+                "phpunit/phpunit": "^4.8|^5.7",
+                "symfony/finder": "^2.8"
             },
             "suggest": {
-                "ext-soap": "*"
+                "ext-dom": "Needed to make use of the features in the utilities namespace"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "5.0.x-dev"
-                }
-            },
             "autoload": {
-                "classmap": [
-                    "src/"
+                "psr-0": {
+                    "Highlight\\": "",
+                    "HighlightUtilities\\": ""
+                },
+                "files": [
+                    "HighlightUtilities/functions.php"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
             ],
             "authors": [
                 {
-                    "name": "Sebastian Bergmann",
-                    "email": "[email protected]",
-                    "role": "lead"
+                    "name": "Geert Bergman",
+                    "role": "Project Author",
+                    "homepage": "http://www.scrivo.org/"
+                },
+                {
+                    "name": "Vladimir Jimenez",
+                    "role": "Contributor",
+                    "homepage": "https://allejo.io"
+                },
+                {
+                    "name": "Martin Folkers",
+                    "role": "Contributor",
+                    "homepage": "https://twobrain.io"
                 }
             ],
-            "description": "Mock Object library for PHPUnit",
-            "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+            "description": "Server side syntax highlighter that supports 185 languages. It's a PHP port of highlight.js",
             "keywords": [
-                "mock",
-                "xunit"
+                "code",
+                "highlight",
+                "highlight.js",
+                "highlight.php",
+                "syntax"
             ],
-            "abandoned": true,
-            "time": "2018-08-09T05:50:03+00:00"
+            "time": "2019-08-27T04:27:48+00:00"
         },
         {
             "name": "sebastian/code-unit-reverse-lookup",
         },
         {
             "name": "sebastian/comparator",
-            "version": "2.1.3",
+            "version": "3.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/comparator.git",
-                "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9"
+                "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9",
-                "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
+                "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.0",
-                "sebastian/diff": "^2.0 || ^3.0",
+                "php": "^7.1",
+                "sebastian/diff": "^3.0",
                 "sebastian/exporter": "^3.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.4"
+                "phpunit/phpunit": "^7.1"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.1.x-dev"
+                    "dev-master": "3.0-dev"
                 }
             },
             "autoload": {
                 "compare",
                 "equality"
             ],
-            "time": "2018-02-01T13:46:46+00:00"
+            "time": "2018-07-12T15:12:46+00:00"
         },
         {
             "name": "sebastian/diff",
-            "version": "2.0.1",
+            "version": "3.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/diff.git",
-                "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd"
+                "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd",
-                "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
+                "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.0"
+                "php": "^7.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.2"
+                "phpunit/phpunit": "^7.5 || ^8.0",
+                "symfony/process": "^2 || ^3.3 || ^4"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0-dev"
+                    "dev-master": "3.0-dev"
                 }
             },
             "autoload": {
             "description": "Diff implementation",
             "homepage": "https://github.com/sebastianbergmann/diff",
             "keywords": [
-                "diff"
+                "diff",
+                "udiff",
+                "unidiff",
+                "unified diff"
             ],
-            "time": "2017-08-03T08:09:46+00:00"
+            "time": "2019-02-04T06:01:07+00:00"
         },
         {
             "name": "sebastian/environment",
-            "version": "3.1.0",
+            "version": "4.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/environment.git",
-                "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5"
+                "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5",
-                "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404",
+                "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.0"
+                "php": "^7.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.1"
+                "phpunit/phpunit": "^7.5"
+            },
+            "suggest": {
+                "ext-posix": "*"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1.x-dev"
+                    "dev-master": "4.2-dev"
                 }
             },
             "autoload": {
                 "environment",
                 "hhvm"
             ],
-            "time": "2017-07-01T08:51:00+00:00"
+            "time": "2019-05-05T09:05:15+00:00"
         },
         {
             "name": "sebastian/exporter",
-            "version": "3.1.0",
+            "version": "3.1.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/exporter.git",
-                "reference": "234199f4528de6d12aaa58b612e98f7d36adb937"
+                "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937",
-                "reference": "234199f4528de6d12aaa58b612e98f7d36adb937",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e",
+                "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e",
                 "shasum": ""
             },
             "require": {
                 "BSD-3-Clause"
             ],
             "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "[email protected]"
+                },
                 {
                     "name": "Jeff Welch",
                     "email": "[email protected]"
                     "name": "Volker Dusch",
                     "email": "[email protected]"
                 },
-                {
-                    "name": "Bernhard Schussek",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "[email protected]"
-                },
                 {
                     "name": "Adam Harvey",
                     "email": "[email protected]"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "[email protected]"
                 }
             ],
             "description": "Provides the functionality to export PHP variables for visualization",
                 "export",
                 "exporter"
             ],
-            "time": "2017-04-03T13:19:02+00:00"
+            "time": "2019-09-14T09:02:43+00:00"
+        },
+        {
+            "name": "sebastian/finder-facade",
+            "version": "1.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/finder-facade.git",
+                "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f",
+                "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f",
+                "shasum": ""
+            },
+            "require": {
+                "symfony/finder": "~2.3|~3.0|~4.0",
+                "theseer/fdomdocument": "~1.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "[email protected]",
+                    "role": "lead"
+                }
+            ],
+            "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.",
+            "homepage": "https://github.com/sebastianbergmann/finder-facade",
+            "time": "2017-11-18T17:31:49+00:00"
         },
         {
             "name": "sebastian/global-state",
-            "version": "2.0.0",
+            "version": "3.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/global-state.git",
-                "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
+                "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
-                "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4",
+                "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.0"
+                "php": "^7.2",
+                "sebastian/object-reflector": "^1.1.1",
+                "sebastian/recursion-context": "^3.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.0"
+                "ext-dom": "*",
+                "phpunit/phpunit": "^8.0"
             },
             "suggest": {
                 "ext-uopz": "*"
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0-dev"
+                    "dev-master": "3.0-dev"
                 }
             },
             "autoload": {
             "keywords": [
                 "global state"
             ],
-            "time": "2017-04-27T15:39:26+00:00"
+            "time": "2019-02-01T05:30:01+00:00"
         },
         {
             "name": "sebastian/object-enumerator",
         },
         {
             "name": "sebastian/resource-operations",
-            "version": "1.0.0",
+            "version": "2.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/resource-operations.git",
-                "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
+                "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
-                "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+                "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9",
+                "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.6.0"
+                "php": "^7.1"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "2.0-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Provides a list of PHP built-in functions that operate on resources",
             "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
-            "time": "2015-07-28T20:34:47+00:00"
+            "time": "2018-10-04T04:07:39+00:00"
+        },
+        {
+            "name": "sebastian/type",
+            "version": "1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/type.git",
+                "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3",
+                "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "[email protected]",
+                    "role": "lead"
+                }
+            ],
+            "description": "Collection of value objects that represent the types of the PHP type system",
+            "homepage": "https://github.com/sebastianbergmann/type",
+            "time": "2019-07-02T08:10:15+00:00"
         },
         {
             "name": "sebastian/version",
         },
         {
             "name": "squizlabs/php_codesniffer",
-            "version": "3.4.0",
+            "version": "3.4.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
-                "reference": "379deb987e26c7cd103a7b387aea178baec96e48"
+                "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/379deb987e26c7cd103a7b387aea178baec96e48",
-                "reference": "379deb987e26c7cd103a7b387aea178baec96e48",
+                "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8",
+                "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8",
                 "shasum": ""
             },
             "require": {
                 }
             ],
             "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
-            "homepage": "http://www.squizlabs.com/php-codesniffer",
+            "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
             "keywords": [
                 "phpcs",
                 "standards"
             ],
-            "time": "2018-12-19T23:57:18+00:00"
+            "time": "2019-04-10T23:49:02+00:00"
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v3.1.10",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "7eede2a901a19928494194f7d1815a77b9a473a0"
+                "reference": "cc686552948d627528c0e2e759186dff67c2610e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7eede2a901a19928494194f7d1815a77b9a473a0",
-                "reference": "7eede2a901a19928494194f7d1815a77b9a473a0",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/cc686552948d627528c0e2e759186dff67c2610e",
+                "reference": "cc686552948d627528c0e2e759186dff67c2610e",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^7.1.3",
+                "symfony/polyfill-ctype": "~1.8",
                 "symfony/polyfill-mbstring": "~1.0"
             },
+            "conflict": {
+                "masterminds/html5": "<2.6"
+            },
             "require-dev": {
-                "symfony/css-selector": "~2.8|~3.0"
+                "masterminds/html5": "^2.6",
+                "symfony/css-selector": "~3.4|~4.0"
             },
             "suggest": {
                 "symfony/css-selector": ""
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony DomCrawler Component",
             "homepage": "https://symfony.com",
-            "time": "2017-01-21T17:13:55+00:00"
+            "time": "2019-08-26T08:26:39+00:00"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v3.3.6",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "427987eb4eed764c3b6e38d52a0f87989e010676"
+                "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/427987eb4eed764c3b6e38d52a0f87989e010676",
-                "reference": "427987eb4eed764c3b6e38d52a0f87989e010676",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/9abbb7ef96a51f4d7e69627bc6f63307994e4263",
+                "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^7.1.3",
+                "symfony/polyfill-ctype": "~1.8"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2017-07-11T07:17:58+00:00"
+            "time": "2019-08-20T14:07:54+00:00"
+        },
+        {
+            "name": "theseer/fdomdocument",
+            "version": "1.6.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/theseer/fDOMDocument.git",
+                "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/6e8203e40a32a9c770bcb62fe37e68b948da6dca",
+                "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "lib-libxml": "*",
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "role": "lead",
+                    "email": "[email protected]"
+                }
+            ],
+            "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.",
+            "homepage": "https://github.com/theseer/fDOMDocument",
+            "time": "2017-06-30T11:53:12+00:00"
         },
         {
             "name": "theseer/tokenizer",
-            "version": "1.1.0",
+            "version": "1.1.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/theseer/tokenizer.git",
-                "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b"
+                "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b",
-                "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b",
+                "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
+                "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
                 "shasum": ""
             },
             "require": {
             "authors": [
                 {
                     "name": "Arne Blankerts",
-                    "email": "[email protected]",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "email": "[email protected]"
                 }
             ],
             "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
-            "time": "2017-04-07T12:08:54+00:00"
+            "time": "2019-06-13T22:48:21+00:00"
         },
         {
             "name": "webmozart/assert",
-            "version": "1.4.0",
+            "version": "1.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/webmozart/assert.git",
-                "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9"
+                "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9",
-                "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9",
+                "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4",
+                "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4",
                 "shasum": ""
             },
             "require": {
                 "symfony/polyfill-ctype": "^1.8"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.6",
-                "sebastian/version": "^1.0.1"
+                "phpunit/phpunit": "^4.8.36 || ^7.5.13"
             },
             "type": "library",
             "extra": {
                 "check",
                 "validate"
             ],
-            "time": "2018-12-25T11:19:39+00:00"
+            "time": "2019-08-24T08:43:50+00:00"
+        },
+        {
+            "name": "wnx/laravel-stats",
+            "version": "v2.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/stefanzweifel/laravel-stats.git",
+                "reference": "1b3c60bfbf81233973cbc2a63be4e6f83b2d6205"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/stefanzweifel/laravel-stats/zipball/1b3c60bfbf81233973cbc2a63be4e6f83b2d6205",
+                "reference": "1b3c60bfbf81233973cbc2a63be4e6f83b2d6205",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/console": "~5.8.0|^6.0",
+                "illuminate/support": "~5.8.0|^6.0",
+                "php": ">=7.2.0",
+                "phploc/phploc": "~4.0|~5.0",
+                "symfony/finder": "~3.3|~4.0"
+            },
+            "require-dev": {
+                "laravel/browser-kit-testing": "~2.0|~3.0|~4.0|~5.0",
+                "laravel/dusk": "~3.0|~4.0|~5.0",
+                "mockery/mockery": "^1.1",
+                "orchestra/testbench": "^3.8",
+                "phpunit/phpunit": "6.*|7.*|8.*"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "Wnx\\LaravelStats\\StatsServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Wnx\\LaravelStats\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Stefan Zweifel",
+                    "email": "[email protected]",
+                    "homepage": "https://stefanzweifel.io",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Get insights about your Laravel Project",
+            "homepage": "https://github.com/stefanzweifel/laravel-stats",
+            "keywords": [
+                "laravel",
+                "statistics",
+                "stats",
+                "wnx"
+            ],
+            "time": "2019-09-01T14:18:49+00:00"
         }
     ],
     "aliases": [],
-    "minimum-stability": "stable",
-    "stability-flags": {
-        "laravel/socialite": 20
-    },
-    "prefer-stable": false,
+    "minimum-stability": "dev",
+    "stability-flags": [],
+    "prefer-stable": true,
     "prefer-lowest": false,
     "platform": {
-        "php": ">=7.0.5",
-        "ext-json": "*",
-        "ext-tidy": "*",
+        "php": "^7.2",
+        "ext-curl": "*",
         "ext-dom": "*",
-        "ext-xml": "*",
-        "ext-mbstring": "*",
         "ext-gd": "*",
-        "ext-curl": "*"
+        "ext-json": "*",
+        "ext-mbstring": "*",
+        "ext-tidy": "*",
+        "ext-xml": "*"
     },
     "platform-dev": [],
     "platform-overrides": {
-        "php": "7.0.5"
+        "php": "7.2.0"
     }
 }
diff --git a/crowdin.yml b/crowdin.yml
new file mode 100644 (file)
index 0000000..634a577
--- /dev/null
@@ -0,0 +1,7 @@
+project_identifier: bookstack
+base_path: .
+preserve_hierarchy: false
+files:
+  - source: /resources/lang/en/*.php
+    translation: /resources/lang/%two_letters_code%/%original_file_name%
+    type: php
index de6b0b276899b976afd5f78235a6b5d7ec803f75..ddf3c295d22e09023fad7e62eaefc5c198f454a1 100644 (file)
@@ -15,8 +15,8 @@ $factory->define(\BookStack\Auth\User::class, function ($faker) {
     return [
         'name' => $faker->name,
         'email' => $faker->email,
-        'password' => str_random(10),
-        'remember_token' => str_random(10),
+        'password' => Str::random(10),
+        'remember_token' => Str::random(10),
         'email_confirmed' => 1
     ];
 });
@@ -24,7 +24,7 @@ $factory->define(\BookStack\Auth\User::class, function ($faker) {
 $factory->define(\BookStack\Entities\Bookshelf::class, function ($faker) {
     return [
         'name' => $faker->sentence,
-        'slug' => str_random(10),
+        'slug' => Str::random(10),
         'description' => $faker->paragraph
     ];
 });
@@ -32,7 +32,7 @@ $factory->define(\BookStack\Entities\Bookshelf::class, function ($faker) {
 $factory->define(\BookStack\Entities\Book::class, function ($faker) {
     return [
         'name' => $faker->sentence,
-        'slug' => str_random(10),
+        'slug' => Str::random(10),
         'description' => $faker->paragraph
     ];
 });
@@ -40,7 +40,7 @@ $factory->define(\BookStack\Entities\Book::class, function ($faker) {
 $factory->define(\BookStack\Entities\Chapter::class, function ($faker) {
     return [
         'name' => $faker->sentence,
-        'slug' => str_random(10),
+        'slug' => Str::random(10),
         'description' => $faker->paragraph
     ];
 });
@@ -49,7 +49,7 @@ $factory->define(\BookStack\Entities\Page::class, function ($faker) {
     $html = '<p>' . implode('</p>', $faker->paragraphs(5)) . '</p>';
     return [
         'name' => $faker->sentence,
-        'slug' => str_random(10),
+        'slug' => Str::random(10),
         'html' => $html,
         'text' => strip_tags($html),
         'revision_count' => 1
diff --git a/database/migrations/2019_07_07_112515_add_template_support.php b/database/migrations/2019_07_07_112515_add_template_support.php
new file mode 100644 (file)
index 0000000..a545081
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+
+use Carbon\Carbon;
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class AddTemplateSupport extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('pages', function (Blueprint $table) {
+            $table->boolean('template')->default(false);
+            $table->index('template');
+        });
+
+        // Create new templates-manage permission and assign to admin role
+        $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id;
+        $permissionId = DB::table('role_permissions')->insertGetId([
+            'name' => 'templates-manage',
+            'display_name' => 'Manage Page Templates',
+            'created_at' => Carbon::now()->toDateTimeString(),
+            'updated_at' => Carbon::now()->toDateTimeString()
+        ]);
+        DB::table('permission_role')->insert([
+            'role_id' => $adminRoleId,
+            'permission_id' => $permissionId
+        ]);
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('pages', function (Blueprint $table) {
+            $table->dropColumn('template');
+        });
+
+        // Remove templates-manage permission
+        $templatesManagePermission = DB::table('role_permissions')
+            ->where('name', '=', 'templates_manage')->first();
+
+        DB::table('permission_role')->where('permission_id', '=', $templatesManagePermission->id)->delete();
+        DB::table('role_permissions')->where('name', '=', 'templates_manage')->delete();
+    }
+}
diff --git a/database/migrations/2019_08_17_140214_add_user_invites_table.php b/database/migrations/2019_08_17_140214_add_user_invites_table.php
new file mode 100644 (file)
index 0000000..23bd698
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class AddUserInvitesTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('user_invites', function (Blueprint $table) {
+            $table->increments('id');
+            $table->integer('user_id')->index();
+            $table->string('token')->index();
+            $table->nullableTimestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('user_invites');
+    }
+}
index 988ea2100519ef6598cc0b13f69105e8b8682355..d86cb0dddd06947d478dde126f7ae8c685ec17a9 100644 (file)
@@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model;
 class DatabaseSeeder extends Seeder
 {
     /**
-     * Run the database seeds.
+     * Seed the application's database.
      *
      * @return void
      */
index ce3cd1307d6f5279f0f1a37ee830bd5d7c661a80..deb1aa11c3b3b196add089e024404eb7c73555a8 100644 (file)
@@ -1,6 +1,14 @@
 <?php
 
+use BookStack\Auth\Permissions\PermissionService;
+use BookStack\Auth\Role;
+use BookStack\Auth\User;
+use BookStack\Entities\Bookshelf;
+use BookStack\Entities\Chapter;
+use BookStack\Entities\Page;
+use BookStack\Entities\SearchService;
 use Illuminate\Database\Seeder;
+use Illuminate\Support\Str;
 
 class DummyContentSeeder extends Seeder
 {
@@ -12,39 +20,39 @@ class DummyContentSeeder extends Seeder
     public function run()
     {
         // Create an editor user
-        $editorUser = factory(\BookStack\Auth\User::class)->create();
-        $editorRole = \BookStack\Auth\Role::getRole('editor');
+        $editorUser = factory(User::class)->create();
+        $editorRole = Role::getRole('editor');
         $editorUser->attachRole($editorRole);
 
         // Create a viewer user
-        $viewerUser = factory(\BookStack\Auth\User::class)->create();
-        $role = \BookStack\Auth\Role::getRole('viewer');
+        $viewerUser = factory(User::class)->create();
+        $role = Role::getRole('viewer');
         $viewerUser->attachRole($role);
 
         $byData = ['created_by' => $editorUser->id, 'updated_by' => $editorUser->id];
 
         factory(\BookStack\Entities\Book::class, 5)->create($byData)
             ->each(function($book) use ($editorUser, $byData) {
-                $chapters = factory(\BookStack\Entities\Chapter::class, 3)->create($byData)
+                $chapters = factory(Chapter::class, 3)->create($byData)
                     ->each(function($chapter) use ($editorUser, $book, $byData){
-                        $pages = factory(\BookStack\Entities\Page::class, 3)->make(array_merge($byData, ['book_id' => $book->id]));
+                        $pages = factory(Page::class, 3)->make(array_merge($byData, ['book_id' => $book->id]));
                         $chapter->pages()->saveMany($pages);
                     });
-                $pages = factory(\BookStack\Entities\Page::class, 3)->make($byData);
+                $pages = factory(Page::class, 3)->make($byData);
                 $book->chapters()->saveMany($chapters);
                 $book->pages()->saveMany($pages);
             });
 
-        $largeBook = factory(\BookStack\Entities\Book::class)->create(array_merge($byData, ['name' => 'Large book' . str_random(10)]));
-        $pages = factory(\BookStack\Entities\Page::class, 200)->make($byData);
-        $chapters = factory(\BookStack\Entities\Chapter::class, 50)->make($byData);
+        $largeBook = factory(\BookStack\Entities\Book::class)->create(array_merge($byData, ['name' => 'Large book' . Str::random(10)]));
+        $pages = factory(Page::class, 200)->make($byData);
+        $chapters = factory(Chapter::class, 50)->make($byData);
         $largeBook->pages()->saveMany($pages);
         $largeBook->chapters()->saveMany($chapters);
 
-        $shelves = factory(\BookStack\Entities\Bookshelf::class, 10)->create($byData);
+        $shelves = factory(Bookshelf::class, 10)->create($byData);
         $largeBook->shelves()->attach($shelves->pluck('id'));
 
-        app(\BookStack\Auth\Permissions\PermissionService::class)->buildJointPermissions();
-        app(\BookStack\Entities\SearchService::class)->indexAllEntities();
+        app(PermissionService::class)->buildJointPermissions();
+        app(SearchService::class)->indexAllEntities();
     }
 }
index 136b6cb6a9f6465627bdab0ccab31f7fd8c24112..4db10395adf037a48aac650a19b2cc02748f3f84 100644 (file)
@@ -1,6 +1,13 @@
 <?php
 
+use BookStack\Auth\Permissions\PermissionService;
+use BookStack\Auth\Role;
+use BookStack\Auth\User;
+use BookStack\Entities\Chapter;
+use BookStack\Entities\Page;
+use BookStack\Entities\SearchService;
 use Illuminate\Database\Seeder;
+use Illuminate\Support\Str;
 
 class LargeContentSeeder extends Seeder
 {
@@ -12,16 +19,16 @@ class LargeContentSeeder extends Seeder
     public function run()
     {
         // Create an editor user
-        $editorUser = factory(\BookStack\Auth\User::class)->create();
-        $editorRole = \BookStack\Auth\Role::getRole('editor');
+        $editorUser = factory(User::class)->create();
+        $editorRole = Role::getRole('editor');
         $editorUser->attachRole($editorRole);
 
-        $largeBook = factory(\BookStack\Entities\Book::class)->create(['name' => 'Large book' . str_random(10), 'created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
-        $pages = factory(\BookStack\Entities\Page::class, 200)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
-        $chapters = factory(\BookStack\Entities\Chapter::class, 50)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
+        $largeBook = factory(\BookStack\Entities\Book::class)->create(['name' => 'Large book' . Str::random(10), 'created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
+        $pages = factory(Page::class, 200)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
+        $chapters = factory(Chapter::class, 50)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
         $largeBook->pages()->saveMany($pages);
         $largeBook->chapters()->saveMany($chapters);
-        app(\BookStack\Auth\Permissions\PermissionService::class)->buildJointPermissions();
-        app(\BookStack\Entities\SearchService::class)->indexAllEntities();
+        app(PermissionService::class)->buildJointPermissions();
+        app(SearchService::class)->indexAllEntities();
     }
 }
diff --git a/dev/docker/Dockerfile b/dev/docker/Dockerfile
new file mode 100644 (file)
index 0000000..8816615
--- /dev/null
@@ -0,0 +1,16 @@
+FROM php:7.3-apache
+
+ENV APACHE_DOCUMENT_ROOT /app/public
+WORKDIR /app
+
+RUN apt-get update -y \
+    && apt-get install -y git zip unzip libtidy-dev libpng-dev libldap2-dev libxml++2.6-dev wait-for-it \
+    && docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu \
+    && docker-php-ext-install pdo pdo_mysql tidy dom xml mbstring gd ldap \
+    && a2enmod rewrite \
+    && sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
+    && sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
+    && php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
+    && php composer-setup.php \
+    && mv composer.phar /usr/bin/composer \
+    && php -r "unlink('composer-setup.php');"
diff --git a/dev/docker/entrypoint.app.sh b/dev/docker/entrypoint.app.sh
new file mode 100755 (executable)
index 0000000..ff44f0c
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+set -e
+
+env
+
+if [[ -n "$1" ]]; then
+    exec "$@"
+else
+    wait-for-it db:3306 -t 45
+    php artisan migrate --database=mysql
+    chown -R www-data:www-data storage
+    exec apache2-foreground
+fi
\ No newline at end of file
diff --git a/dev/docker/entrypoint.node.sh b/dev/docker/entrypoint.node.sh
new file mode 100755 (executable)
index 0000000..e59e1e8
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+set -e
+
+npm install
+npm rebuild node-sass
+
+exec npm run watch
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644 (file)
index 0000000..ea7a61a
--- /dev/null
@@ -0,0 +1,48 @@
+# This is a Docker Compose configuration
+# intended for development purposes only
+
+version: '3'
+
+volumes:
+  db: {}
+
+services:
+  db:
+    image: mysql:8
+    environment:
+      MYSQL_DATABASE: bookstack-test
+      MYSQL_USER: bookstack-test
+      MYSQL_PASSWORD: bookstack-test
+      MYSQL_RANDOM_ROOT_PASSWORD: 'true'
+    command: --default-authentication-plugin=mysql_native_password
+    volumes:
+      - db:/var/lib/mysql
+  app:
+    build:
+      context: .
+      dockerfile: ./dev/docker/Dockerfile
+    environment:
+      DB_CONNECTION: mysql
+      DB_HOST: db
+      DB_PORT: 3306
+      DB_DATABASE: bookstack-test
+      DB_USERNAME: bookstack-test
+      DB_PASSWORD: bookstack-test
+      MAIL_DRIVER: smtp
+      MAIL_HOST: mailhog
+      MAIL_PORT: 1025
+    ports:
+      - ${DEV_PORT:-8080}:80
+    volumes:
+      - ./:/app
+    entrypoint: /app/dev/docker/entrypoint.app.sh
+  node:
+    image: node:alpine
+    working_dir: /app
+    volumes:
+      - ./:/app
+    entrypoint: /app/dev/docker/entrypoint.node.sh
+  mailhog:
+    image: mailhog/mailhog
+    ports:
+      - ${DEV_MAIL_PORT:-8025}:8025
index 352ad5ce9445cb4197e8d0bc0add1dd0d4531298..47afc27a15d7109a2bdec012a5735641f71ef44a 100644 (file)
         "anymatch": "^2.0.0",
         "async-each": "^1.0.1",
         "braces": "^2.3.2",
+        "fsevents": "^1.2.7",
         "glob-parent": "^3.1.0",
         "inherits": "^2.0.3",
         "is-binary-path": "^1.0.0",
       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
       "dev": true
     },
+    "fsevents": {
+      "version": "1.2.9",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz",
+      "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "nan": "^2.12.1",
+        "node-pre-gyp": "^0.12.0"
+      },
+      "dependencies": {
+        "abbrev": {
+          "version": "1.1.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "ansi-regex": {
+          "version": "2.1.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "aproba": {
+          "version": "1.2.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "are-we-there-yet": {
+          "version": "1.1.5",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "delegates": "^1.0.0",
+            "readable-stream": "^2.0.6"
+          }
+        },
+        "balanced-match": {
+          "version": "1.0.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "brace-expansion": {
+          "version": "1.1.11",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "balanced-match": "^1.0.0",
+            "concat-map": "0.0.1"
+          }
+        },
+        "chownr": {
+          "version": "1.1.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "code-point-at": {
+          "version": "1.1.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "concat-map": {
+          "version": "0.0.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "console-control-strings": {
+          "version": "1.1.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "core-util-is": {
+          "version": "1.0.2",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "debug": {
+          "version": "4.1.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "deep-extend": {
+          "version": "0.6.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "delegates": {
+          "version": "1.0.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "detect-libc": {
+          "version": "1.0.3",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "fs-minipass": {
+          "version": "1.2.5",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "minipass": "^2.2.1"
+          }
+        },
+        "fs.realpath": {
+          "version": "1.0.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "gauge": {
+          "version": "2.7.4",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "aproba": "^1.0.3",
+            "console-control-strings": "^1.0.0",
+            "has-unicode": "^2.0.0",
+            "object-assign": "^4.1.0",
+            "signal-exit": "^3.0.0",
+            "string-width": "^1.0.1",
+            "strip-ansi": "^3.0.1",
+            "wide-align": "^1.1.0"
+          }
+        },
+        "glob": {
+          "version": "7.1.3",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "has-unicode": {
+          "version": "2.0.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "iconv-lite": {
+          "version": "0.4.24",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "safer-buffer": ">= 2.1.2 < 3"
+          }
+        },
+        "ignore-walk": {
+          "version": "3.0.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "minimatch": "^3.0.4"
+          }
+        },
+        "inflight": {
+          "version": "1.0.6",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "once": "^1.3.0",
+            "wrappy": "1"
+          }
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "ini": {
+          "version": "1.3.5",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        },
+        "isarray": {
+          "version": "1.0.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "minimatch": {
+          "version": "3.0.4",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "brace-expansion": "^1.1.7"
+          }
+        },
+        "minimist": {
+          "version": "0.0.8",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "minipass": {
+          "version": "2.3.5",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "safe-buffer": "^5.1.2",
+            "yallist": "^3.0.0"
+          }
+        },
+        "minizlib": {
+          "version": "1.2.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "minipass": "^2.2.1"
+          }
+        },
+        "mkdirp": {
+          "version": "0.5.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "minimist": "0.0.8"
+          }
+        },
+        "ms": {
+          "version": "2.1.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "needle": {
+          "version": "2.3.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "debug": "^4.1.0",
+            "iconv-lite": "^0.4.4",
+            "sax": "^1.2.4"
+          }
+        },
+        "node-pre-gyp": {
+          "version": "0.12.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "detect-libc": "^1.0.2",
+            "mkdirp": "^0.5.1",
+            "needle": "^2.2.1",
+            "nopt": "^4.0.1",
+            "npm-packlist": "^1.1.6",
+            "npmlog": "^4.0.2",
+            "rc": "^1.2.7",
+            "rimraf": "^2.6.1",
+            "semver": "^5.3.0",
+            "tar": "^4"
+          }
+        },
+        "nopt": {
+          "version": "4.0.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "abbrev": "1",
+            "osenv": "^0.1.4"
+          }
+        },
+        "npm-bundled": {
+          "version": "1.0.6",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "npm-packlist": {
+          "version": "1.4.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "ignore-walk": "^3.0.1",
+            "npm-bundled": "^1.0.1"
+          }
+        },
+        "npmlog": {
+          "version": "4.1.2",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "are-we-there-yet": "~1.1.2",
+            "console-control-strings": "~1.1.0",
+            "gauge": "~2.7.3",
+            "set-blocking": "~2.0.0"
+          }
+        },
+        "number-is-nan": {
+          "version": "1.0.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "once": {
+          "version": "1.4.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "wrappy": "1"
+          }
+        },
+        "os-homedir": {
+          "version": "1.0.2",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "os-tmpdir": {
+          "version": "1.0.2",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "osenv": {
+          "version": "0.1.5",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "os-homedir": "^1.0.0",
+            "os-tmpdir": "^1.0.0"
+          }
+        },
+        "path-is-absolute": {
+          "version": "1.0.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "process-nextick-args": {
+          "version": "2.0.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "rc": {
+          "version": "1.2.8",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "deep-extend": "^0.6.0",
+            "ini": "~1.3.0",
+            "minimist": "^1.2.0",
+            "strip-json-comments": "~2.0.1"
+          },
+          "dependencies": {
+            "minimist": {
+              "version": "1.2.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            }
+          }
+        },
+        "readable-stream": {
+          "version": "2.3.6",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "rimraf": {
+          "version": "2.6.3",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "glob": "^7.1.3"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "safer-buffer": {
+          "version": "2.1.2",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "sax": {
+          "version": "1.2.4",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "semver": {
+          "version": "5.7.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "set-blocking": {
+          "version": "2.0.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "signal-exit": {
+          "version": "3.0.2",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "code-point-at": "^1.0.0",
+            "is-fullwidth-code-point": "^1.0.0",
+            "strip-ansi": "^3.0.0"
+          }
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "ansi-regex": "^2.0.0"
+          }
+        },
+        "strip-json-comments": {
+          "version": "2.0.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "tar": {
+          "version": "4.4.8",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "chownr": "^1.1.1",
+            "fs-minipass": "^1.2.5",
+            "minipass": "^2.3.4",
+            "minizlib": "^1.1.1",
+            "mkdirp": "^0.5.0",
+            "safe-buffer": "^5.1.2",
+            "yallist": "^3.0.2"
+          }
+        },
+        "util-deprecate": {
+          "version": "1.0.2",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "wide-align": {
+          "version": "1.1.3",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "string-width": "^1.0.2 || 2"
+          }
+        },
+        "wrappy": {
+          "version": "1.0.2",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "yallist": {
+          "version": "3.0.3",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        }
+      }
+    },
     "fstream": {
       "version": "1.0.12",
       "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
       }
     },
     "lodash": {
-      "version": "4.17.11",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-      "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
+      "version": "4.17.15",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
       "dev": true
     },
     "lodash.tail": {
       }
     },
     "mixin-deep": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
-      "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+      "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
       "dev": true,
       "requires": {
         "for-in": "^1.0.2",
       "dev": true
     },
     "set-value": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
-      "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+      "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
       "dev": true,
       "requires": {
         "extend-shallow": "^2.0.1",
       "dev": true
     },
     "union-value": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
-      "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+      "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
       "dev": true,
       "requires": {
         "arr-union": "^3.1.0",
         "get-value": "^2.0.6",
         "is-extendable": "^0.1.1",
-        "set-value": "^0.4.3"
-      },
-      "dependencies": {
-        "extend-shallow": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
-          "requires": {
-            "is-extendable": "^0.1.0"
-          }
-        },
-        "set-value": {
-          "version": "0.4.3",
-          "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
-          "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
-          "dev": true,
-          "requires": {
-            "extend-shallow": "^2.0.1",
-            "is-extendable": "^0.1.1",
-            "is-plain-object": "^2.0.1",
-            "to-object-path": "^0.3.0"
-          }
-        }
+        "set-value": "^2.0.1"
       }
     },
     "uniq": {
index 53722a71b4e193a9a1949322c3581bfacc293c08..9f83e95ff238ea30a9672077b9fef6add7a885ab 100644 (file)
@@ -7,8 +7,7 @@
          convertNoticesToExceptions="true"
          convertWarningsToExceptions="true"
          processIsolation="false"
-         stopOnFailure="false"
-         syntaxCheck="false">
+         stopOnFailure="false">
     <testsuites>
         <testsuite name="Application Test Suite">
             <directory>./tests/</directory>
         </whitelist>
     </filter>
     <php>
-        <env name="APP_ENV" value="testing"/>
-        <env name="APP_DEBUG" value="false"/>
-        <env name="APP_LANG" value="en"/>
-        <env name="APP_AUTO_LANG_PUBLIC" value="true"/>
-        <env name="CACHE_DRIVER" value="array"/>
-        <env name="SESSION_DRIVER" value="array"/>
-        <env name="QUEUE_DRIVER" value="sync"/>
-        <env name="DB_CONNECTION" value="mysql_testing"/>
-        <env name="MAIL_DRIVER" value="log"/>
-        <env name="AUTH_METHOD" value="standard"/>
-        <env name="DISABLE_EXTERNAL_SERVICES" value="true"/>
-        <env name="AVATAR_URL" value=""/>
-        <env name="LDAP_VERSION" value="3"/>
-        <env name="STORAGE_TYPE" value="local"/>
-        <env name="STORAGE_ATTACHMENT_TYPE" value="local"/>
-        <env name="STORAGE_IMAGE_TYPE" value="local"/>
-        <env name="GITHUB_APP_ID" value="aaaaaaaaaaaaaa"/>
-        <env name="GITHUB_APP_SECRET" value="aaaaaaaaaaaaaa"/>
-        <env name="GITHUB_AUTO_REGISTER" value=""/>
-        <env name="GITHUB_AUTO_CONFIRM_EMAIL" value=""/>
-        <env name="GOOGLE_APP_ID" value="aaaaaaaaaaaaaa"/>
-        <env name="GOOGLE_APP_SECRET" value="aaaaaaaaaaaaaa"/>
-        <env name="GOOGLE_AUTO_REGISTER" value=""/>
-        <env name="GOOGLE_AUTO_CONFIRM_EMAIL" value=""/>
-        <env name="GOOGLE_SELECT_ACCOUNT" value=""/>
-        <env name="APP_URL" value="http://bookstack.dev"/>
-        <env name="DEBUGBAR_ENABLED" value="false"/>
+        <server name="APP_ENV" value="testing"/>
+        <server name="APP_DEBUG" value="false"/>
+        <server name="APP_LANG" value="en"/>
+        <server name="APP_AUTO_LANG_PUBLIC" value="true"/>
+        <server name="CACHE_DRIVER" value="array"/>
+        <server name="SESSION_DRIVER" value="array"/>
+        <server name="QUEUE_CONNECTION" value="sync"/>
+        <server name="DB_CONNECTION" value="mysql_testing"/>
+        <server name="BCRYPT_ROUNDS" value="4"/>
+        <server name="MAIL_DRIVER" value="array"/>
+        <server name="LOG_CHANNEL" value="single"/>
+        <server name="AUTH_METHOD" value="standard"/>
+        <server name="DISABLE_EXTERNAL_SERVICES" value="true"/>
+        <server name="AVATAR_URL" value=""/>
+        <server name="LDAP_VERSION" value="3"/>
+        <server name="STORAGE_TYPE" value="local"/>
+        <server name="STORAGE_ATTACHMENT_TYPE" value="local"/>
+        <server name="STORAGE_IMAGE_TYPE" value="local"/>
+        <server name="GITHUB_APP_ID" value="aaaaaaaaaaaaaa"/>
+        <server name="GITHUB_APP_SECRET" value="aaaaaaaaaaaaaa"/>
+        <server name="GITHUB_AUTO_REGISTER" value=""/>
+        <server name="GITHUB_AUTO_CONFIRM_EMAIL" value=""/>
+        <server name="GOOGLE_APP_ID" value="aaaaaaaaaaaaaa"/>
+        <server name="GOOGLE_APP_SECRET" value="aaaaaaaaaaaaaa"/>
+        <server name="GOOGLE_AUTO_REGISTER" value=""/>
+        <server name="GOOGLE_AUTO_CONFIRM_EMAIL" value=""/>
+        <server name="GOOGLE_SELECT_ACCOUNT" value=""/>
+        <server name="APP_URL" value="http://bookstack.dev"/>
+        <server name="DEBUGBAR_ENABLED" value="false"/>
     </php>
 </phpunit>
index 8eb2dd0ddfa5f7b57bd0f351684dfa2e4e3ac0d3..0d55354ec6fb756086be2bcace7be11078064f29 100644 (file)
@@ -1,6 +1,6 @@
 <IfModule mod_rewrite.c>
     <IfModule mod_negotiation.c>
-        Options -MultiViews
+        Options -MultiViews -Indexes
     </IfModule>
 
     RewriteEngine On
index c96a04f008ee21e260b28f7701595ed59e2839e3..cb7328e1934841ba2afd78b3b7ceb100961337bb 100755 (executable)
@@ -1,2 +1,3 @@
 *
-!.gitignore
\ No newline at end of file
+!.gitignore
+!.htaccess
\ No newline at end of file
diff --git a/public/uploads/.htaccess b/public/uploads/.htaccess
new file mode 100755 (executable)
index 0000000..45552cb
--- /dev/null
@@ -0,0 +1 @@
+Options -Indexes
\ No newline at end of file
diff --git a/public/web.config b/public/web.config
new file mode 100644 (file)
index 0000000..474eb68
--- /dev/null
@@ -0,0 +1,28 @@
+<!--
+    Rewrites requires Microsoft URL Rewrite Module for IIS
+    Download: https://www.microsoft.com/en-us/download/details.aspx?id=47337
+    Debug Help: https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-failed-request-tracing-to-trace-rewrite-rules
+-->
+<configuration>
+  <system.webServer>
+    <rewrite>
+      <rules>
+        <rule name="Imported Rule 1" stopProcessing="true">
+          <match url="^(.*)/$" ignoreCase="false" />
+          <conditions>
+            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
+          </conditions>
+          <action type="Redirect" redirectType="Permanent" url="/{R:1}" />
+        </rule>
+        <rule name="Imported Rule 2" stopProcessing="true">
+          <match url="^" ignoreCase="false" />
+          <conditions>
+            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
+            <add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
+          </conditions>
+          <action type="Rewrite" url="index.php" />
+        </rule>
+      </rules>
+    </rewrite>
+  </system.webServer>
+</configuration>
\ No newline at end of file
index 276a3de2017e35fdf3856c55ba4826a8054ccfe9..ca90be3053af821b307e8b77bd72548f846056fa 100644 (file)
--- a/readme.md
+++ b/readme.md
@@ -2,7 +2,8 @@
 
 [![GitHub release](https://img.shields.io/github/release/BookStackApp/BookStack.svg)](https://github.com/BookStackApp/BookStack/releases/latest)
 [![license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/BookStackApp/BookStack/blob/master/LICENSE)
-[![Build Status](https://travis-ci.org/BookStackApp/BookStack.svg)](https://travis-ci.org/BookStackApp/BookStack)
+[![Build Status](https://github.com/BookStackApp/BookStack/workflows/phpunit/badge.svg)](https://github.com/BookStackApp/BookStack/actions)
+[![Discord](https://img.shields.io/static/v1?label=Chat&message=Discord&color=738adb&logo=discord)](https://discord.gg/ztkBqR2)
 
 A platform for storing and organising information and documentation. General information and documentation for BookStack can be found at https://www.bookstackapp.com/.
 
@@ -12,7 +13,7 @@ A platform for storing and organising information and documentation. General inf
     * [Admin Login](https://demo.bookstackapp.com/[email protected]&password=password)
 * [BookStack Blog](https://www.bookstackapp.com/blog)
 
-## Project Definition
+## 📚 Project Definition
 
 BookStack is an opinionated wiki system that provides a pleasant and simple out of the box experience. New users to an instance should find the experience intuitive and only basic word-processing skills should be required to get involved in creating content on BookStack. The platform should provide advanced power features to those that desire it but they should not interfere with the core simple user experience.
 
@@ -20,7 +21,7 @@ BookStack is not designed as an extensible platform to be used for purposes that
 
 In regards to development philosophy, BookStack has a relaxed, open & positive approach. At the end of the day this is free software developed and maintained by people donating their own free time.
 
-## Road Map
+## 🛣️ Road Map
 
 Below is a high-level road map view for BookStack to provide a sense of direction of where the project is going. This can change at any point and does not reflect many features and improvements that will also be included as part of the journey along this road map. For more granular detail of what will be included in upcoming releases you can review the project milestones as defined in the "Release Process" section below.
 
@@ -33,7 +34,7 @@ Below is a high-level road map view for BookStack to provide a sense of directio
 - **Installation & Deployment Process Revamp**
     - *Creation of a streamlined & secure process for users to deploy & update BookStack with reduced development requirements (No git or composer requirement).*
 
-## Release Versioning & Process
+## 🚀 Release Versioning & Process
 
 BookStack releases are each assigned a version number, such as "v0.25.2", in the format `v<phase>.<feature>.<patch>`. A change only in the `patch` number indicates a fairly minor release that mainly contains fixes and therefore is very unlikely to cause breakages upon update. A change in the `feature` number indicates a release which will generally bring new features in addition to fixes and enhancements. These releases have a small chance of introducing breaking changes upon update so it's worth checking for any notes in the [update guide](https://www.bookstackapp.com/docs/admin/updates/). A change in the `phase` indicates a much large change in BookStack that will likely incur breakages requiring manual intervention.
 
@@ -41,7 +42,7 @@ Each BookStack release will have a [milestone](https://github.com/BookStackApp/B
 
 For feature releases, and some patch releases, the release will be accompanied by a post on the [BookStack blog](https://www.bookstackapp.com/blog/) which will provide additional detail on features, changes & updates otherwise the [GitHub release page](https://github.com/BookStackApp/BookStack/releases) will show a list of changes. You can sign up to be alerted to new BookStack blogs posts (once per week maximum) [at this link](http://eepurl.com/cmmq5j).
 
-## Development & Testing
+## 🛠️ Development & Testing
 
 All development on BookStack is currently done on the master branch. When it's time for a release the master branch is merged into release with built & minified CSS & JS then tagged at its version. Here are the current development requirements:
 
@@ -74,7 +75,37 @@ php artisan db:seed --class=DummyContentSeeder --database=mysql_testing
 
 Once done you can run `php vendor/bin/phpunit` in the application root directory to run all tests.
 
-## Translations
+### 📜 Code Standards
+
+PHP code within BookStack is generally to [PSR-2](http://www.php-fig.org/psr/psr-2/) standards. From the BookStack root folder you can run `./vendor/bin/phpcs` to check code is formatted correctly and `./vendor/bin/phpcbf` to auto-fix non-PSR-2 code.
+
+### 🐋 Development using Docker
+
+This repository ships with a Docker Compose configuration intended for development purposes. It'll build a PHP image with all needed extensions installed and start up a MySQL server and a Node image watching the UI assets.
+
+To get started, make sure you meet the following requirements:
+
+- Docker and Docker Compose are installed
+- Your user is part of the `docker` group
+
+If all the conditions are met, you can proceed with the following steps:
+
+1. Install PHP/Composer dependencies with **`docker-compose run app composer install`** (first time can take a while because the image has to be built).
+2. **Copy `.env.example` to `.env`** and change `APP_KEY` to a random 32 char string.
+3. Make sure **port 8080 is unused** *or else* change `DEV_PORT` to a free port on your host.
+4. **Run `chgrp -R docker storage`**. The development container will chown the `storage` directory to the `www-data` user inside the container so BookStack can write to it. You need to change the group to your host's `docker` group here to not lose access to the `storage` directory.
+5. **Run `docker-compose up`** and wait until all database migrations have been done.
+6. You can now login with `[email protected]` and `password` as password on `localhost:8080` (or another port if specified).
+
+If needed, You'll be able to run any artisan commands via docker-compose like so:
+
+ ```shell script
+docker-compose run app php artisan list 
+```
+
+The docker-compose setup runs an instance of [MailHog](https://github.com/mailhog/MailHog) and sets environment variables to redirect any BookStack-sent emails to MailHog. You can view this mail via the MailHog web interface on `localhost:8025`. You can change the port MailHog is accessible on by setting a `DEV_MAIL_PORT` environment variable.
+
+## 🌎 Translations
 
 All text strings can be found in the `resources/lang` folder where each language option has its own folder. To add a new language you should copy the `en` folder to an new folder (eg. `fr` for french) then go through and translate all text strings in those files, leaving the keys and file-names intact. If a language string is missing then the `en` translation will be used. To show the language option in the user preferences language drop-down you will need to add your language to the options found at the bottom of the `resources/lang/en/settings.php` file. A system-wide language can also be set in the `.env` file like so: `APP_LANG=en`.
 
@@ -93,42 +124,35 @@ php resources/lang/check.php pt_BR
 
 Some strings have colon-prefixed variables in such as `:userName`. Leave these values as they are as they will be replaced at run-time.
 
-## Contributing & Maintenance
+## 🎁 Contributing, Issues & Pull Requests
 
-Feel free to create issues to request new features or to report bugs and problems. Just please follow the template given when creating the issue.
+Feel free to create issues to request new features or to report bugs & problems. Just please follow the template given when creating the issue.
+
+Pull requests are welcome. Unless a small tweak or language update, It may be best to open the pull request early or create an issue for your intended change to discuss how it will fit in to the project and plan out the merge. Pull requests should be created from the `master` branch since they will be merged back into `master` once done. Please do not build from or request a merge into the `release` branch as this is only for publishing releases. If you are looking to alter CSS or JavaScript content please edit the source files found in `resources/assets`. Any CSS or JS files within `public` are built from these source files and therefore should not be edited directly.
 
 The project's code of conduct [can be found here](https://github.com/BookStackApp/BookStack/blob/master/.github/CODE_OF_CONDUCT.md).
 
-### Code Standards
+## 🔒 Security
 
-PHP code within BookStack is generally to [PSR-2](http://www.php-fig.org/psr/psr-2/) standards. From the BookStack root folder you can run `./vendor/bin/phpcs` to check code is formatted correctly and `./vendor/bin/phpcbf` to auto-fix non-PSR-2 code.
+Security information for administering a BookStack instance can be found on the [documentation site here](https://www.bookstackapp.com/docs/admin/security/).
 
-### Pull Requests
+If you'd like to be notified of new potential security concerns you can [sign-up to the BookStack security mailing list](http://eepurl.com/glIh8z).
 
-Pull requests are welcome. Unless a small tweak or language update, It may be best to open the pull request early or create an issue for your intended change to discuss how it will fit in to the project and plan out the merge.
+If you would like to report a security concern in a more confidential manner than via a GitHub issue, You can directly email the lead maintainer [ssddanbrown](https://github.com/ssddanbrown). You will need to login to be able to see the email address on the [GitHub profile page](https://github.com/ssddanbrown). Alternatively you can send a DM via twitter to [@ssddanbrown](https://twitter.com/ssddanbrown).
 
-Pull requests should be created from the `master` branch since they will be merged back into `master` once done. Please do not build from or request a merge into the `release` branch as this is only for publishing releases.
+## ♿ Accessibility
 
-If you are looking to alter CSS or JavaScript content please edit the source files found in `resources/assets`. Any CSS or JS files within `public` are built from these source files and therefore should not be edited directly.
+We want BookStack to remain accessible to as many people as possible. We aim for at least WCAG 2.1 Level A standards where possible although we do not strictly test this upon each release. If you come across any accessibility issues please feel free to open an issue.
 
-## Website, Docs & Blog
+## 🖥️ Website, Docs & Blog
 
 The website which contains the project docs & Blog can be found in the [BookStackApp/website](https://github.com/BookStackApp/website) repo.
 
-## Security
-
-Security information for administering a BookStack instance can be found on the [documentation site here](https://www.bookstackapp.com/docs/admin/security/).
-
-If you'd like to be notified of new potential security concerns you can [sign-up to the BookStack security mailing list](http://eepurl.com/glIh8z).
-
-If you would like to report a security concern in a more confidential manner than via a GitHub issue, You can directly email the lead maintainer [ssddanbrown](https://github.com/ssddanbrown). You will need to login to be able to see the email address on the [GitHub profile page](https://github.com/ssddanbrown). Alternatively you can send a DM via twitter to [@ssddanbrown](https://twitter.com/ssddanbrown).
-
-
-## License
+## ⚖️ License
 
-The BookStack source is provided under the MIT License.
+The BookStack source is provided under the MIT License. The libraries used by, and included with, BookStack are provided under their own licenses.
 
-## Attribution
+## 👪 Attribution
 
 The great people that have worked to build and improve BookStack can [be seen here](https://github.com/BookStackApp/BookStack/graphs/contributors).
 
@@ -150,3 +174,4 @@ These are the great open-source projects used to help build BookStack:
     * [Laravel IDE helper](https://github.com/barryvdh/laravel-ide-helper)
 * [WKHTMLtoPDF](http://wkhtmltopdf.org/index.html)
 * [Draw.io](https://github.com/jgraph/drawio)
+* [Laravel Stats](https://github.com/stefanzweifel/laravel-stats)
\ No newline at end of file
diff --git a/resources/assets/js/components/dropdown.js b/resources/assets/js/components/dropdown.js
deleted file mode 100644 (file)
index 3887e84..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- * Dropdown
- * Provides some simple logic to create simple dropdown menus.
- */
-class DropDown {
-
-    constructor(elem) {
-        this.container = elem;
-        this.menu = elem.querySelector('.dropdown-menu, [dropdown-menu]');
-        this.moveMenu = elem.hasAttribute('dropdown-move-menu');
-        this.toggle = elem.querySelector('[dropdown-toggle]');
-        this.body = document.body;
-        this.setupListeners();
-    }
-
-    show(event) {
-        this.hideAll();
-
-        this.menu.style.display = 'block';
-        this.menu.classList.add('anim', 'menuIn');
-
-        if (this.moveMenu) {
-            // Move to body to prevent being trapped within scrollable sections
-            this.rect = this.menu.getBoundingClientRect();
-            this.body.appendChild(this.menu);
-            this.menu.style.position = 'fixed';
-            this.menu.style.left = `${this.rect.left}px`;
-            this.menu.style.top = `${this.rect.top}px`;
-            this.menu.style.width = `${this.rect.width}px`;
-        }
-
-        // Set listener to hide on mouse leave or window click
-        this.menu.addEventListener('mouseleave', this.hide.bind(this));
-        window.addEventListener('click', event => {
-            if (!this.menu.contains(event.target)) {
-                this.hide();
-            }
-        });
-
-        // Focus on first input if existing
-        let input = this.menu.querySelector('input');
-        if (input !== null) input.focus();
-
-        event.stopPropagation();
-    }
-
-    hideAll() {
-        for (let dropdown of window.components.dropdown) {
-            dropdown.hide();
-        }
-    }
-
-    hide() {
-        this.menu.style.display = 'none';
-        this.menu.classList.remove('anim', 'menuIn');
-        if (this.moveMenu) {
-            this.menu.style.position = '';
-            this.menu.style.left = '';
-            this.menu.style.top = '';
-            this.menu.style.width = '';
-            this.container.appendChild(this.menu);
-        }
-    }
-
-    setupListeners() {
-        // Hide menu on option click
-        this.container.addEventListener('click', event => {
-             let possibleChildren = Array.from(this.menu.querySelectorAll('a'));
-             if (possibleChildren.indexOf(event.target) !== -1) this.hide();
-        });
-        // Show dropdown on toggle click
-        this.toggle.addEventListener('click', this.show.bind(this));
-        // Hide menu on enter press
-        this.container.addEventListener('keypress', event => {
-                if (event.keyCode !== 13) return true;
-                event.preventDefault();
-                this.hide();
-                return false;
-        });
-    }
-
-}
-
-export default DropDown;
\ No newline at end of file
diff --git a/resources/assets/js/services/events.js b/resources/assets/js/services/events.js
deleted file mode 100644 (file)
index 1f97d0c..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Simple global events manager
- */
-class Events {
-    constructor() {
-        this.listeners = {};
-        this.stack = [];
-    }
-
-    emit(eventName, eventData) {
-        this.stack.push({name: eventName, data: eventData});
-        if (typeof this.listeners[eventName] === 'undefined') return this;
-        let eventsToStart = this.listeners[eventName];
-        for (let i = 0; i < eventsToStart.length; i++) {
-            let event = eventsToStart[i];
-            event(eventData);
-        }
-        return this;
-    }
-
-    listen(eventName, callback) {
-        if (typeof this.listeners[eventName] === 'undefined') this.listeners[eventName] = [];
-        this.listeners[eventName].push(callback);
-        return this;
-    }
-}
-
-export default Events;
\ No newline at end of file
diff --git a/resources/assets/sass/print-styles.scss b/resources/assets/sass/print-styles.scss
deleted file mode 100644 (file)
index 44107f2..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-@import "variables";
-
-header {
-  display: none;
-}
-
-body {
-  font-size: 12px;
-}
-
-.page-content {
-  margin: 0 auto;
-}
-
-.flex-fill {
-  display: block;
-}
-
-.flex.sidebar + .flex.content {
-  border-left: none;
-}
-
-.print-hidden {
-  display: none;
-}
-
-.print-full-width {
-  width: 100%;
-  float: none;
-  display: block;
-}
-
-h2 {
-  font-size: 2em;
-  line-height: 1;
-  margin-top: 0.6em;
-  margin-bottom: 0.3em;
-}
-
-.comments-container {
-  display: none;
-}
\ No newline at end of file
diff --git a/resources/icons/chevron-down.svg b/resources/icons/chevron-down.svg
new file mode 100644 (file)
index 0000000..f08dfaf
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7.41 8L12 12.58 16.59 8 18 9.41l-6 6-6-6z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
\ No newline at end of file
diff --git a/resources/icons/template.svg b/resources/icons/template.svg
new file mode 100644 (file)
index 0000000..7c14212
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 4h7V2H4c-1.1 0-2 .9-2 2v7h2zm16-2h-7v2h7v7h2V4c0-1.1-.9-2-2-2zm0 18h-7v2h7c1.1 0 2-.9 2-2v-7h-2zM4 13H2v7c0 1.1.9 2 2 2h7v-2H4zM16.475 15.356h-8.95v-2.237h8.95zm0-4.475h-8.95V8.644h8.95z"/></svg>
\ No newline at end of file
similarity index 63%
rename from resources/assets/js/components/breadcrumb-listing.js
rename to resources/js/components/breadcrumb-listing.js
index 11e1522db30753356cc90ee969269055d3235389..7f4344b17d7f1b4452f920d30eda7a3d4d72049e 100644 (file)
@@ -7,35 +7,14 @@ class BreadcrumbListing {
         this.searchInput = elem.querySelector('input');
         this.loadingElem = elem.querySelector('.loading-container');
         this.entityListElem = elem.querySelector('.breadcrumb-listing-entity-list');
-        this.toggleElem = elem.querySelector('[dropdown-toggle]');
 
         // this.loadingElem.style.display = 'none';
         const entityDescriptor = elem.getAttribute('breadcrumb-listing').split(':');
         this.entityType = entityDescriptor[0];
         this.entityId = Number(entityDescriptor[1]);
 
-        this.toggleElem.addEventListener('click', this.onShow.bind(this));
+        this.elem.addEventListener('show', this.onShow.bind(this));
         this.searchInput.addEventListener('input', this.onSearch.bind(this));
-        this.elem.addEventListener('keydown', this.keyDown.bind(this));
-    }
-
-    keyDown(event) {
-        if (event.key === 'ArrowDown') {
-            this.listFocusChange(1);
-            event.preventDefault();
-        } else if  (event.key === 'ArrowUp') {
-            this.listFocusChange(-1);
-            event.preventDefault();
-        }
-    }
-
-    listFocusChange(indexChange = 1) {
-        const links = Array.from(this.entityListElem.querySelectorAll('a:not(.hidden)'));
-        const currentFocused = this.entityListElem.querySelector('a:focus');
-        const currentFocusedIndex = links.indexOf(currentFocused);
-        const defaultFocus = (indexChange > 0) ? links[0] : this.searchInput;
-        const nextElem = links[currentFocusedIndex + indexChange] || defaultFocus;
-        nextElem.focus();
     }
 
     onShow() {
similarity index 86%
rename from resources/assets/js/components/chapter-toggle.js
rename to resources/js/components/chapter-toggle.js
index a751206d16afa1dd2b76708c33f90131f9579c83..bfd0ac7296576f6151aa61e686a3591fd6b72e6a 100644 (file)
@@ -11,12 +11,14 @@ class ChapterToggle {
     open() {
         const list = this.elem.parentNode.querySelector('.inset-list');
         this.elem.classList.add('open');
+        this.elem.setAttribute('aria-expanded', 'true');
         slideDown(list, 240);
     }
 
     close() {
         const list = this.elem.parentNode.querySelector('.inset-list');
         this.elem.classList.remove('open');
+        this.elem.setAttribute('aria-expanded', 'false');
         slideUp(list, 240);
     }
 
similarity index 84%
rename from resources/assets/js/components/collapsible.js
rename to resources/js/components/collapsible.js
index 40ab325082df08a3a2cc37c968a3ffeabe6675b8..464f394c1e7e42a8d1dc568c94568d53f1498819 100644 (file)
@@ -18,11 +18,13 @@ class Collapsible {
 
     open() {
         this.elem.classList.add('open');
+        this.trigger.setAttribute('aria-expanded', 'true');
         slideDown(this.content, 300);
     }
 
     close() {
         this.elem.classList.remove('open');
+        this.trigger.setAttribute('aria-expanded', 'false');
         slideUp(this.content, 300);
     }
 
diff --git a/resources/js/components/dropdown.js b/resources/js/components/dropdown.js
new file mode 100644 (file)
index 0000000..4de1e23
--- /dev/null
@@ -0,0 +1,152 @@
+import {onSelect} from "../services/dom";
+
+/**
+ * Dropdown
+ * Provides some simple logic to create simple dropdown menus.
+ */
+class DropDown {
+
+    constructor(elem) {
+        this.container = elem;
+        this.menu = elem.querySelector('.dropdown-menu, [dropdown-menu]');
+        this.moveMenu = elem.hasAttribute('dropdown-move-menu');
+        this.toggle = elem.querySelector('[dropdown-toggle]');
+        this.body = document.body;
+        this.showing = false;
+        this.setupListeners();
+    }
+
+    show(event = null) {
+        this.hideAll();
+
+        this.menu.style.display = 'block';
+        this.menu.classList.add('anim', 'menuIn');
+        this.toggle.setAttribute('aria-expanded', 'true');
+
+        if (this.moveMenu) {
+            // Move to body to prevent being trapped within scrollable sections
+            this.rect = this.menu.getBoundingClientRect();
+            this.body.appendChild(this.menu);
+            this.menu.style.position = 'fixed';
+            this.menu.style.left = `${this.rect.left}px`;
+            this.menu.style.top = `${this.rect.top}px`;
+            this.menu.style.width = `${this.rect.width}px`;
+        }
+
+        // Set listener to hide on mouse leave or window click
+        this.menu.addEventListener('mouseleave', this.hide.bind(this));
+        window.addEventListener('click', event => {
+            if (!this.menu.contains(event.target)) {
+                this.hide();
+            }
+        });
+
+        // Focus on first input if existing
+        const input = this.menu.querySelector('input');
+        if (input !== null) input.focus();
+
+        this.showing = true;
+
+        const showEvent = new Event('show');
+        this.container.dispatchEvent(showEvent);
+
+        if (event) {
+            event.stopPropagation();
+        }
+    }
+
+    hideAll() {
+        for (let dropdown of window.components.dropdown) {
+            dropdown.hide();
+        }
+    }
+
+    hide() {
+        this.menu.style.display = 'none';
+        this.menu.classList.remove('anim', 'menuIn');
+        this.toggle.setAttribute('aria-expanded', 'false');
+        if (this.moveMenu) {
+            this.menu.style.position = '';
+            this.menu.style.left = '';
+            this.menu.style.top = '';
+            this.menu.style.width = '';
+            this.container.appendChild(this.menu);
+        }
+        this.showing = false;
+    }
+
+    getFocusable() {
+        return Array.from(this.menu.querySelectorAll('[tabindex],[href],button,input:not([type=hidden])'));
+    }
+
+    focusNext() {
+        const focusable = this.getFocusable();
+        const currentIndex = focusable.indexOf(document.activeElement);
+        let newIndex = currentIndex + 1;
+        if (newIndex >= focusable.length) {
+            newIndex = 0;
+        }
+
+        focusable[newIndex].focus();
+    }
+
+    focusPrevious() {
+        const focusable = this.getFocusable();
+        const currentIndex = focusable.indexOf(document.activeElement);
+        let newIndex = currentIndex - 1;
+        if (newIndex < 0) {
+            newIndex = focusable.length - 1;
+        }
+
+        focusable[newIndex].focus();
+    }
+
+    setupListeners() {
+        // Hide menu on option click
+        this.container.addEventListener('click', event => {
+             const possibleChildren = Array.from(this.menu.querySelectorAll('a'));
+             if (possibleChildren.includes(event.target)) {
+                 this.hide();
+             }
+        });
+
+        onSelect(this.toggle, event => {
+            event.stopPropagation();
+            this.show(event);
+            if (event instanceof KeyboardEvent) {
+                this.focusNext();
+            }
+        });
+
+        // Keyboard navigation
+        const keyboardNavigation = event => {
+            if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
+                this.focusNext();
+                event.preventDefault();
+            } else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
+                this.focusPrevious();
+                event.preventDefault();
+            } else if (event.key === 'Escape') {
+                this.hide();
+                this.toggle.focus();
+                event.stopPropagation();
+            }
+        };
+        this.container.addEventListener('keydown', keyboardNavigation);
+        if (this.moveMenu) {
+            this.menu.addEventListener('keydown', keyboardNavigation);
+        }
+
+        // Hide menu on enter press or escape
+        this.menu.addEventListener('keydown ', event => {
+            if (event.key === 'Enter') {
+                event.preventDefault();
+                event.stopPropagation();
+                this.hide();
+            }
+        });
+    }
+
+}
+
+export default DropDown;
\ No newline at end of file
similarity index 90%
rename from resources/assets/js/components/editor-toolbox.js
rename to resources/js/components/editor-toolbox.js
index 10678edfaa4e880c5d3f076b061fd968abdb54a0..354bf0a86b3262f4a5e14a9e446ea87ee9405c65 100644 (file)
@@ -23,6 +23,8 @@ class EditorToolbox {
 
     toggle() {
         this.elem.classList.toggle('open');
+        const expanded = this.elem.classList.contains('open') ? 'true' : 'false';
+        this.toggleButton.setAttribute('aria-expanded', expanded);
     }
 
     setActiveTab(tabName, openToolbox = false) {
similarity index 93%
rename from resources/assets/js/components/index.js
rename to resources/js/components/index.js
index 1c2abd5202e1b47b964cbb5d0239fddddf12019e..14cf08ae2da41014e4703c57bfae6f79c4dc1c9e 100644 (file)
@@ -27,6 +27,8 @@ import customCheckbox from "./custom-checkbox";
 import bookSort from "./book-sort";
 import settingAppColorPicker from "./setting-app-color-picker";
 import entityPermissionsEditor from "./entity-permissions-editor";
+import templateManager from "./template-manager";
+import newUserPassword from "./new-user-password";
 
 const componentMapping = {
     'dropdown': dropdown,
@@ -57,7 +59,9 @@ const componentMapping = {
     'custom-checkbox': customCheckbox,
     'book-sort': bookSort,
     'setting-app-color-picker': settingAppColorPicker,
-    'entity-permissions-editor': entityPermissionsEditor
+    'entity-permissions-editor': entityPermissionsEditor,
+    'template-manager': templateManager,
+    'new-user-password': newUserPassword,
 };
 
 window.components = {};
similarity index 82%
rename from resources/assets/js/components/markdown-editor.js
rename to resources/js/components/markdown-editor.js
index b0e4d693a4e499810ad5bb40e549815095a9b8ec..de256a8466a727add893f5370167f3db4ef34320 100644 (file)
@@ -18,19 +18,24 @@ class MarkdownEditor {
         this.markdown.use(mdTasksLists, {label: true});
 
         this.display = this.elem.querySelector('.markdown-display');
+
+        this.displayStylesLoaded = false;
         this.input = this.elem.querySelector('textarea');
         this.htmlInput = this.elem.querySelector('input[name=html]');
         this.cm = code.markdownEditor(this.input);
 
         this.onMarkdownScroll = this.onMarkdownScroll.bind(this);
-        this.init();
 
-        // Scroll to text if needed.
-        const queryParams = (new URL(window.location)).searchParams;
-        const scrollText = queryParams.get('content-text');
-        if (scrollText) {
-            this.scrollToText(scrollText);
-        }
+        this.display.addEventListener('load', () => {
+            this.displayDoc = this.display.contentDocument;
+            this.init();
+        });
+
+        window.$events.emitPublic(elem, 'editor-markdown::setup', {
+            markdownIt: this.markdown,
+            displayEl: this.display,
+            codeMirrorInstance: this.cm,
+        });
     }
 
     init() {
@@ -38,7 +43,7 @@ class MarkdownEditor {
         let lastClick = 0;
 
         // Prevent markdown display link click redirect
-        this.display.addEventListener('click', event => {
+        this.displayDoc.addEventListener('click', event => {
             let isDblClick = Date.now() - lastClick < 300;
 
             let link = event.target.closest('a');
@@ -91,21 +96,49 @@ class MarkdownEditor {
         });
 
         this.codeMirrorSetup();
+        this.listenForBookStackEditorEvents();
+
+        // Scroll to text if needed.
+        const queryParams = (new URL(window.location)).searchParams;
+        const scrollText = queryParams.get('content-text');
+        if (scrollText) {
+            this.scrollToText(scrollText);
+        }
     }
 
     // Update the input content and render the display.
     updateAndRender() {
-        let content = this.cm.getValue();
+        const content = this.cm.getValue();
         this.input.value = content;
-        let html = this.markdown.render(content);
+        const html = this.markdown.render(content);
         window.$events.emit('editor-html-change', html);
         window.$events.emit('editor-markdown-change', content);
-        this.display.innerHTML = html;
+
+        // Set body content
+        this.displayDoc.body.className = 'page-content';
+        this.displayDoc.body.innerHTML = html;
         this.htmlInput.value = html;
+
+        // Copy styles from page head and set custom styles for editor
+        this.loadStylesIntoDisplay();
+    }
+
+    loadStylesIntoDisplay() {
+        if (this.displayStylesLoaded) return;
+        this.displayDoc.documentElement.className = 'markdown-editor-display';
+
+        this.displayDoc.head.innerHTML = '';
+        const styles = document.head.querySelectorAll('style,link[rel=stylesheet]');
+        for (let style of styles) {
+            const copy = style.cloneNode(true);
+            this.displayDoc.head.appendChild(copy);
+        }
+
+        this.displayStylesLoaded = true;
     }
 
     onMarkdownScroll(lineCount) {
-        const elems = this.display.children;
+        const elems = this.displayDoc.body.children;
         if (elems.length <= lineCount) return;
 
         const topElem = (lineCount === -1) ? elems[elems.length-1] : elems[lineCount];
@@ -199,16 +232,30 @@ class MarkdownEditor {
             }
         });
 
-        // Handle images on drag-drop
+        // Handle image & content drag n drop
         cm.on('drop', (cm, event) => {
-            event.stopPropagation();
-            event.preventDefault();
-            let cursorPos = cm.coordsChar({left: event.pageX, top: event.pageY});
-            cm.setCursor(cursorPos);
-            if (!event.dataTransfer || !event.dataTransfer.files) return;
-            for (let i = 0; i < event.dataTransfer.files.length; i++) {
-                uploadImage(event.dataTransfer.files[i]);
+
+            const templateId = event.dataTransfer.getData('bookstack/template');
+            if (templateId) {
+                const cursorPos = cm.coordsChar({left: event.pageX, top: event.pageY});
+                cm.setCursor(cursorPos);
+                event.preventDefault();
+                window.$http.get(`/templates/${templateId}`).then(resp => {
+                    const content = resp.data.markdown || resp.data.html;
+                    cm.replaceSelection(content);
+                });
+            }
+
+            if (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length > 0) {
+                const cursorPos = cm.coordsChar({left: event.pageX, top: event.pageY});
+                cm.setCursor(cursorPos);
+                event.stopPropagation();
+                event.preventDefault();
+                for (let i = 0; i < event.dataTransfer.files.length; i++) {
+                    uploadImage(event.dataTransfer.files[i]);
+                }
             }
+
         });
 
         // Helper to replace editor content
@@ -461,6 +508,37 @@ class MarkdownEditor {
         })
     }
 
+    listenForBookStackEditorEvents() {
+
+        function getContentToInsert({html, markdown}) {
+            return markdown || html;
+        }
+
+        // Replace editor content
+        window.$events.listen('editor::replace', (eventContent) => {
+            const markdown = getContentToInsert(eventContent);
+            this.cm.setValue(markdown);
+        });
+
+        // Append editor content
+        window.$events.listen('editor::append', (eventContent) => {
+            const cursorPos = this.cm.getCursor('from');
+            const markdown = getContentToInsert(eventContent);
+            const content = this.cm.getValue() + '\n' + markdown;
+            this.cm.setValue(content);
+            this.cm.setCursor(cursorPos.line, cursorPos.ch);
+        });
+
+        // Prepend editor content
+        window.$events.listen('editor::prepend', (eventContent) => {
+            const cursorPos = this.cm.getCursor('from');
+            const markdown = getContentToInsert(eventContent);
+            const content = markdown + '\n' + this.cm.getValue();
+            this.cm.setValue(content);
+            const prependLineCount = markdown.split('\n').length;
+            this.cm.setCursor(cursorPos.line + prependLineCount, cursorPos.ch);
+        });
+    }
 }
 
 export default MarkdownEditor ;
diff --git a/resources/js/components/new-user-password.js b/resources/js/components/new-user-password.js
new file mode 100644 (file)
index 0000000..9c4c21c
--- /dev/null
@@ -0,0 +1,28 @@
+
+class NewUserPassword {
+
+    constructor(elem) {
+        this.elem = elem;
+        this.inviteOption = elem.querySelector('input[name=send_invite]');
+
+        if (this.inviteOption) {
+            this.inviteOption.addEventListener('change', this.inviteOptionChange.bind(this));
+            this.inviteOptionChange();
+        }
+    }
+
+    inviteOptionChange() {
+        const inviting = (this.inviteOption.value === 'true');
+        const passwordBoxes = this.elem.querySelectorAll('input[type=password]');
+        for (const input of passwordBoxes) {
+            input.disabled = inviting;
+        }
+        const container = this.elem.querySelector('#password-input-container');
+        if (container) {
+            container.style.display = inviting ? 'none' : 'block';
+        }
+    }
+
+}
+
+export default NewUserPassword;
\ No newline at end of file
similarity index 73%
rename from resources/assets/js/components/overlay.js
rename to resources/js/components/overlay.js
index 1ba5efceadf553e3800f79ed50746d6984014f9f..ad6a01061ec033c0beaebc33fa003a665b57b0d7 100644 (file)
@@ -6,12 +6,22 @@ class Overlay {
         elem.addEventListener('click', event => {
              if (event.target === elem) return this.hide();
         });
+
+        window.addEventListener('keyup', event => {
+            if (event.key === 'Escape') {
+                this.hide();
+            }
+        });
+
         let closeButtons = elem.querySelectorAll('.popup-header-close');
         for (let i=0; i < closeButtons.length; i++) {
             closeButtons[i].addEventListener('click', this.hide.bind(this));
         }
     }
 
+    hide() { this.toggle(false); }
+    show() { this.toggle(true); }
+
     toggle(show = true) {
         let start = Date.now();
         let duration = 240;
@@ -22,6 +32,9 @@ class Overlay {
             this.container.style.opacity = targetOpacity;
             if (elapsedTime > duration) {
                 this.container.style.display = show ? 'flex' : 'none';
+                if (show) {
+                    this.focusOnBody();
+                }
                 this.container.style.opacity = '';
             } else {
                 requestAnimationFrame(setOpacity.bind(this));
@@ -31,8 +44,12 @@ class Overlay {
         requestAnimationFrame(setOpacity.bind(this));
     }
 
-    hide() { this.toggle(false); }
-    show() { this.toggle(true); }
+    focusOnBody() {
+        const body = this.container.querySelector('.popup-body');
+        if (body) {
+            body.focus();
+        }
+    }
 
 }
 
similarity index 99%
rename from resources/assets/js/components/page-comments.js
rename to resources/js/components/page-comments.js
index cabce91396082cf1944153e274d590643ed25742..5d8d169589804a5c92fd355281353bdcdaf6ed88 100644 (file)
@@ -26,10 +26,12 @@ class PageComments {
 
     handleAction(event) {
         let actionElem = event.target.closest('[action]');
+
         if (event.target.matches('a[href^="#"]')) {
             const id = event.target.href.split('#')[1];
             scrollAndHighlightElement(document.querySelector('#' + id));
         }
+
         if (actionElem === null) return;
         event.preventDefault();
 
similarity index 95%
rename from resources/assets/js/components/setting-app-color-picker.js
rename to resources/js/components/setting-app-color-picker.js
index 064596d86e421c9e22e42d2a0ac4298493a34fcc..6c0c0b31dcecb86fbf39fea7c6dbecb496d1822f 100644 (file)
@@ -10,7 +10,7 @@ class SettingAppColorPicker {
         this.colorInput.addEventListener('change', this.updateColor.bind(this));
         this.colorInput.addEventListener('input', this.updateColor.bind(this));
         this.resetButton.addEventListener('click', event => {
-            this.colorInput.value = '#0288D1';
+            this.colorInput.value = '#206ea7';
             this.updateColor();
         });
     }
diff --git a/resources/js/components/template-manager.js b/resources/js/components/template-manager.js
new file mode 100644 (file)
index 0000000..d004a43
--- /dev/null
@@ -0,0 +1,94 @@
+import * as DOM from "../services/dom";
+
+class TemplateManager {
+
+    constructor(elem) {
+        this.elem = elem;
+        this.list = elem.querySelector('[template-manager-list]');
+        this.searching = false;
+
+        // Template insert action buttons
+        DOM.onChildEvent(this.elem, '[template-action]', 'click', this.handleTemplateActionClick.bind(this));
+
+        // Template list pagination click
+        DOM.onChildEvent(this.elem, '.pagination a', 'click', this.handlePaginationClick.bind(this));
+
+        // Template list item content click
+        DOM.onChildEvent(this.elem, '.template-item-content', 'click', this.handleTemplateItemClick.bind(this));
+
+        // Template list item drag start
+        DOM.onChildEvent(this.elem, '.template-item', 'dragstart', this.handleTemplateItemDragStart.bind(this));
+
+        this.setupSearchBox();
+    }
+
+    handleTemplateItemClick(event, templateItem) {
+        const templateId = templateItem.closest('[template-id]').getAttribute('template-id');
+        this.insertTemplate(templateId, 'replace');
+    }
+
+    handleTemplateItemDragStart(event, templateItem) {
+        const templateId = templateItem.closest('[template-id]').getAttribute('template-id');
+        event.dataTransfer.setData('bookstack/template', templateId);
+        event.dataTransfer.setData('text/plain', templateId);
+    }
+
+    handleTemplateActionClick(event, actionButton) {
+        event.stopPropagation();
+
+        const action = actionButton.getAttribute('template-action');
+        const templateId = actionButton.closest('[template-id]').getAttribute('template-id');
+        this.insertTemplate(templateId, action);
+    }
+
+    async insertTemplate(templateId, action = 'replace') {
+        const resp = await window.$http.get(`/templates/${templateId}`);
+        const eventName = 'editor::' + action;
+        window.$events.emit(eventName, resp.data);
+    }
+
+    async handlePaginationClick(event, paginationLink) {
+        event.preventDefault();
+        const paginationUrl = paginationLink.getAttribute('href');
+        const resp = await window.$http.get(paginationUrl);
+        this.list.innerHTML = resp.data;
+    }
+
+    setupSearchBox() {
+        const searchBox = this.elem.querySelector('.search-box');
+        const input = searchBox.querySelector('input');
+        const submitButton = searchBox.querySelector('button');
+        const cancelButton = searchBox.querySelector('button.search-box-cancel');
+
+        async function performSearch() {
+            const searchTerm = input.value;
+            const resp = await window.$http.get(`/templates`, {
+                search: searchTerm
+            });
+            cancelButton.style.display = searchTerm ? 'block' : 'none';
+            this.list.innerHTML = resp.data;
+        }
+        performSearch = performSearch.bind(this);
+
+        // Searchbox enter press
+        searchBox.addEventListener('keypress', event => {
+            if (event.key === 'Enter') {
+                event.preventDefault();
+                performSearch();
+            }
+        });
+
+        // Submit button press
+        submitButton.addEventListener('click', event => {
+            performSearch();
+        });
+
+        // Cancel button press
+        cancelButton.addEventListener('click', event => {
+            input.value = '';
+            performSearch();
+        });
+    }
+}
+
+export default TemplateManager;
\ No newline at end of file
similarity index 62%
rename from resources/assets/js/components/toggle-switch.js
rename to resources/js/components/toggle-switch.js
index 3dd1ce85ccb4bd3d94983e34605a2651d3d47be0..b9b96afc5d07728d992e9cd49eab9e29a6df1f2a 100644 (file)
@@ -11,6 +11,11 @@ class ToggleSwitch {
 
     stateChange() {
         this.input.value = (this.checkbox.checked ? 'true' : 'false');
+
+        // Dispatch change event from hidden input so they can be listened to
+        // like a normal checkbox.
+        const changeEvent = new Event('change');
+        this.input.dispatchEvent(changeEvent);
     }
 
 }
similarity index 93%
rename from resources/assets/js/components/wysiwyg-editor.js
rename to resources/js/components/wysiwyg-editor.js
index eb9f025a749d91edf62fbad3d8be131892523120..60a6743ea7679bdebb7100bd2c047e1b26101166 100644 (file)
@@ -378,6 +378,27 @@ function customHrPlugin() {
 }
 
 
+function listenForBookStackEditorEvents(editor) {
+
+    // Replace editor content
+    window.$events.listen('editor::replace', ({html}) => {
+        editor.setContent(html);
+    });
+
+    // Append editor content
+    window.$events.listen('editor::append', ({html}) => {
+        const content = editor.getContent() + html;
+        editor.setContent(content);
+    });
+
+    // Prepend editor content
+    window.$events.listen('editor::prepend', ({html}) => {
+        const content = html + editor.getContent();
+        editor.setContent(content);
+    });
+
+}
+
 class WysiwygEditor {
 
     constructor(elem) {
@@ -391,6 +412,7 @@ class WysiwygEditor {
         this.loadPlugins();
 
         this.tinyMceConfig = this.getTinyMceConfig();
+        window.$events.emitPublic(elem, 'editor-tinymce::pre-init', {config: this.tinyMceConfig});
         window.tinymce.init(this.tinyMceConfig);
     }
 
@@ -553,6 +575,10 @@ class WysiwygEditor {
                     editor.focus();
                 }
 
+                listenForBookStackEditorEvents(editor);
+
+                // TODO - Update to standardise across both editors
+                // Use events within listenForBookStackEditorEvents instead (Different event signature)
                 window.$events.listen('editor-html-update', html => {
                     editor.setContent(html);
                     editor.selection.select(editor.getBody(), true);
@@ -583,6 +609,18 @@ class WysiwygEditor {
                     let dom = editor.dom,
                         rng = tinymce.dom.RangeUtils.getCaretRangeFromPoint(event.clientX, event.clientY, editor.getDoc());
 
+                    // Template insertion
+                    const templateId = event.dataTransfer.getData('bookstack/template');
+                    if (templateId) {
+                        event.preventDefault();
+                        window.$http.get(`/templates/${templateId}`).then(resp => {
+                            editor.selection.setRng(rng);
+                            editor.undoManager.transact(function () {
+                                editor.execCommand('mceInsertContent', false, resp.data.html);
+                            });
+                        });
+                    }
+
                     // Don't allow anything to be dropped in a captioned image.
                     if (dom.getParent(rng.startContainer, '.mceTemp')) {
                         event.preventDefault();
@@ -617,6 +655,8 @@ class WysiwygEditor {
                 // Paste image-uploads
                 editor.on('paste', event => editorPaste(event, editor, context));
 
+                // Custom handler hook
+                window.$events.emitPublic(context.elem, 'editor-tinymce::setup', {editor});
             }
         };
     }
similarity index 79%
rename from resources/assets/js/services/animations.js
rename to resources/js/services/animations.js
index 8a3e9a57b4ac1a22a932981296570468f4a960f9..b6158ea5f8fbee97872d6350f0a8fa72e9954c13 100644 (file)
@@ -1,3 +1,10 @@
+/**
+ * Used in the function below to store references of clean-up functions.
+ * Used to ensure only one transitionend function exists at any time.
+ * @type {WeakMap<object, any>}
+ */
+const animateStylesCleanupMap = new WeakMap();
+
 /**
  * Fade out the given element.
  * @param {Element} element
@@ -5,6 +12,7 @@
  * @param {Function|null} onComplete
  */
 export function fadeOut(element, animTime = 400, onComplete = null) {
+    cleanupExistingElementAnimation(element);
     animateStyles(element, {
         opacity: ['1', '0']
     }, animTime, () => {
@@ -19,6 +27,7 @@ export function fadeOut(element, animTime = 400, onComplete = null) {
  * @param {Number} animTime
  */
 export function slideUp(element, animTime = 400) {
+    cleanupExistingElementAnimation(element);
     const currentHeight = element.getBoundingClientRect().height;
     const computedStyles = getComputedStyle(element);
     const currentPaddingTop = computedStyles.getPropertyValue('padding-top');
@@ -41,6 +50,7 @@ export function slideUp(element, animTime = 400) {
  * @param {Number} animTime - Animation time in ms
  */
 export function slideDown(element, animTime = 400) {
+    cleanupExistingElementAnimation(element);
     element.style.display = 'block';
     const targetHeight = element.getBoundingClientRect().height;
     const computedStyles = getComputedStyle(element);
@@ -56,13 +66,6 @@ export function slideDown(element, animTime = 400) {
     animateStyles(element, animStyles, animTime);
 }
 
-/**
- * Used in the function below to store references of clean-up functions.
- * Used to ensure only one transitionend function exists at any time.
- * @type {WeakMap<object, any>}
- */
-const animateStylesCleanupMap = new WeakMap();
-
 /**
  * Animate the css styles of an element using FLIP animation techniques.
  * Styles must be an object where the keys are style properties, camelcase, and the values
@@ -84,23 +87,28 @@ function animateStyles(element, styles, animTime = 400, onComplete = null) {
         }
         element.style.transition = null;
         element.removeEventListener('transitionend', cleanup);
+        animateStylesCleanupMap.delete(element);
         if (onComplete) onComplete();
     };
 
     setTimeout(() => {
-        requestAnimationFrame(() => {
-            element.style.transition = `all ease-in-out ${animTime}ms`;
-            for (let style of styleNames) {
-                element.style[style] = styles[style][1];
-            }
+        element.style.transition = `all ease-in-out ${animTime}ms`;
+        for (let style of styleNames) {
+            element.style[style] = styles[style][1];
+        }
 
-            if (animateStylesCleanupMap.has(element)) {
-                const oldCleanup = animateStylesCleanupMap.get(element);
-                element.removeEventListener('transitionend', oldCleanup);
-            }
+        element.addEventListener('transitionend', cleanup);
+        animateStylesCleanupMap.set(element, cleanup);
+    }, 15);
+}
 
-            element.addEventListener('transitionend', cleanup);
-            animateStylesCleanupMap.set(element, cleanup);
-        });
-    }, 10);
+/**
+ * Run the active cleanup action for the given element.
+ * @param {Element} element
+ */
+function cleanupExistingElementAnimation(element) {
+    if (animateStylesCleanupMap.has(element)) {
+        const oldCleanup = animateStylesCleanupMap.get(element);
+        oldCleanup();
+    }
 }
\ No newline at end of file
similarity index 93%
rename from resources/assets/js/services/code.js
rename to resources/js/services/code.js
index 1e0e48289d595132a5ef0e08d006d3c249352d3f..f69f28b8e96f7db6498260681925153acc49f6a0 100644 (file)
@@ -16,6 +16,7 @@ import 'codemirror/mode/mllike/mllike';
 import 'codemirror/mode/nginx/nginx';
 import 'codemirror/mode/php/php';
 import 'codemirror/mode/powershell/powershell';
+import 'codemirror/mode/properties/properties';
 import 'codemirror/mode/python/python';
 import 'codemirror/mode/ruby/ruby';
 import 'codemirror/mode/rust/rust';
@@ -28,6 +29,8 @@ import 'codemirror/mode/yaml/yaml';
 // Addons
 import 'codemirror/addon/scroll/scrollpastend';
 
+// Mapping of potential languages or formats from user input
+// to their proper codemirror modes.
 const modeMap = {
     css: 'css',
     c: 'text/x-csrc',
@@ -42,6 +45,7 @@ const modeMap = {
     haskell: 'haskell',
     hs: 'haskell',
     html: 'htmlmixed',
+    ini: 'properties',
     javascript: 'javascript',
     json: {name: 'javascript', json: true},
     js: 'javascript',
@@ -54,6 +58,7 @@ const modeMap = {
     ml: 'mllike',
     nginx: 'nginx',
     powershell: 'powershell',
+    properties: 'properties',
     ocaml: 'mllike',
     php: 'php',
     py: 'python',
@@ -102,6 +107,7 @@ function highlightElem(elem) {
         value: content,
         mode:  mode,
         lineNumbers: true,
+        lineWrapping: false,
         theme: getTheme(),
         readOnly: true
     });
@@ -188,6 +194,7 @@ function wysiwygView(elem) {
         value: content,
         mode:  getMode(lang),
         lineNumbers: true,
+        lineWrapping: false,
         theme: getTheme(),
         readOnly: true
     });
@@ -213,8 +220,8 @@ function popupEditor(elem, modeSuggestion) {
         value: content,
         mode:  getMode(modeSuggestion),
         lineNumbers: true,
-        theme: getTheme(),
-        lineWrapping: true
+        lineWrapping: false,
+        theme: getTheme()
     });
 }
 
@@ -240,24 +247,27 @@ function setContent(cmInstance, codeContent) {
 }
 
 /**
- * Get a CodeMirror instace to use for the markdown editor.
+ * Get a CodeMirror instance to use for the markdown editor.
  * @param {HTMLElement} elem
  * @returns {*}
  */
 function markdownEditor(elem) {
-    let content = elem.textContent;
-
-    return CodeMirror(function (elt) {
-        elem.parentNode.insertBefore(elt, elem);
-        elem.style.display = 'none';
-    }, {
+    const content = elem.textContent;
+    const config = {
         value: content,
         mode: "markdown",
         lineNumbers: true,
-        theme: getTheme(),
         lineWrapping: true,
+        theme: getTheme(),
         scrollPastEnd: true,
-    });
+    };
+
+    window.$events.emitPublic(elem, 'editor-markdown-cm::pre-init', {config});
+
+    return CodeMirror(function (elt) {
+        elem.parentNode.insertBefore(elt, elem);
+        elem.style.display = 'none';
+    }, config);
 }
 
 /**
similarity index 78%
rename from resources/assets/js/services/dom.js
rename to resources/js/services/dom.js
index 797effd986431cfca0b52075ef6784ced7362539..966a4540e84eeb91117cec8ea89c41609d8b28e6 100644 (file)
@@ -22,6 +22,22 @@ export function onEvents(listenerElement, events, callback) {
     }
 }
 
+/**
+ * Helper to run an action when an element is selected.
+ * A "select" is made to be accessible, So can be a click, space-press or enter-press.
+ * @param listenerElement
+ * @param callback
+ */
+export function onSelect(listenerElement, callback) {
+    listenerElement.addEventListener('click', callback);
+    listenerElement.addEventListener('keydown', (event) => {
+        if (event.key === 'Enter' || event.key === ' ') {
+            event.preventDefault();
+            callback(event);
+        }
+    });
+}
+
 /**
  * Set a listener on an element for an event emitted by a child
  * matching the given childSelector param.
diff --git a/resources/js/services/events.js b/resources/js/services/events.js
new file mode 100644 (file)
index 0000000..fa3ed7f
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * Simple global events manager
+ */
+class Events {
+    constructor() {
+        this.listeners = {};
+        this.stack = [];
+    }
+
+    /**
+     * Emit a custom event for any handlers to pick-up.
+     * @param {String} eventName
+     * @param {*} eventData
+     * @returns {Events}
+     */
+    emit(eventName, eventData) {
+        this.stack.push({name: eventName, data: eventData});
+        if (typeof this.listeners[eventName] === 'undefined') return this;
+        let eventsToStart = this.listeners[eventName];
+        for (let i = 0; i < eventsToStart.length; i++) {
+            let event = eventsToStart[i];
+            event(eventData);
+        }
+        return this;
+    }
+
+    /**
+     * Listen to a custom event and run the given callback when that event occurs.
+     * @param {String} eventName
+     * @param {Function} callback
+     * @returns {Events}
+     */
+    listen(eventName, callback) {
+        if (typeof this.listeners[eventName] === 'undefined') this.listeners[eventName] = [];
+        this.listeners[eventName].push(callback);
+        return this;
+    }
+
+    /**
+     * Emit an event for public use.
+     * Sends the event via the native DOM event handling system.
+     * @param {Element} targetElement
+     * @param {String} eventName
+     * @param {Object} eventData
+     */
+    emitPublic(targetElement, eventName, eventData) {
+        const event = new CustomEvent(eventName, {
+            detail: eventData,
+            bubbles: true
+        });
+        targetElement.dispatchEvent(event);
+    }
+}
+
+export default Events;
\ No newline at end of file
similarity index 98%
rename from resources/assets/js/services/http.js
rename to resources/js/services/http.js
index 06cc6a04ffeab12b73cbfc21b67905f6c6f9e695..06dac9864a4d4523987e61f4009075ea34ed0594 100644 (file)
@@ -67,7 +67,7 @@ async function dataRequest(method, url, data = null) {
         body: data,
     };
 
-    if (typeof data === 'object') {
+    if (typeof data === 'object' && !(data instanceof FormData)) {
         options.headers = {'Content-Type': 'application/json'};
         options.body = JSON.stringify(data);
     }
similarity index 95%
rename from resources/assets/js/services/translations.js
rename to resources/js/services/translations.js
index 645286c08b9238ef8842846a80594cce7733ff51..b595a05e6f95b8e9a2d7862adbc5b5d003f6e163 100644 (file)
@@ -52,9 +52,7 @@ class Translator {
         const rangeRegex = /^\[([0-9]+),([0-9*]+)]/;
         let result = null;
 
-        for (const i = 0, len = splitText.length; i < len; i++) {
-            const t = splitText[i];
-
+        for (let t of splitText) {
             // Parse exact matches
             const exactMatches = t.match(exactCountRegex);
             if (exactMatches !== null && Number(exactMatches[1]) === count) {
@@ -77,7 +75,10 @@ class Translator {
             result = (count === 1) ? splitText[0] : splitText[1];
         }
 
-        if (result === null) result = splitText[0];
+        if (result === null) {
+            result = splitText[0];
+        }
+
         return this.performReplacements(result, replacements);
     }
 
similarity index 89%
rename from resources/assets/js/vues/code-editor.js
rename to resources/js/vues/code-editor.js
index d6f9965a879f0547497d3f046b8056ba5c83ab06..c6df6b1a5de43aafc902fe45fc4a454f2a3ee2dd 100644 (file)
@@ -3,10 +3,10 @@ import codeLib from "../services/code";
 const methods = {
     show() {
         if (!this.editor) this.editor = codeLib.popupEditor(this.$refs.editor, this.language);
-        this.$refs.overlay.style.display = 'flex';
+        this.$refs.overlay.components.overlay.show();
     },
     hide() {
-        this.$refs.overlay.style.display = 'none';
+        this.$refs.overlay.components.overlay.hide();
     },
     updateEditorMode(language) {
         codeLib.setMode(this.editor, language);
similarity index 90%
rename from resources/assets/js/vues/components/autosuggest.js
rename to resources/js/vues/components/autosuggest.js
index d76ee89f189dd517f9805663aa08c06eaf690df5..b809313cb641cc0b723e334f510542fb045975a2 100644 (file)
@@ -6,6 +6,7 @@ const template = `
             @input="inputUpdate($event.target.value)" @focus="inputUpdate($event.target.value)"
             @blur="inputBlur"
             @keydown="inputKeydown"
+            :aria-label="placeholder"
         />
         <ul class="suggestion-box" v-if="showSuggestions">
             <li v-for="(suggestion, i) in suggestions"
@@ -66,23 +67,23 @@ const methods = {
     },
 
     inputKeydown(event) {
-        if (event.keyCode === 13) event.preventDefault();
+        if (event.key === 'Enter') event.preventDefault();
         if (!this.showSuggestions) return;
 
         // Down arrow
-        if (event.keyCode === 40) {
+        if (event.key === 'ArrowDown') {
             this.active = (this.active === this.suggestions.length - 1) ? 0 : this.active+1;
         }
         // Up Arrow
-        else if (event.keyCode === 38) {
+        else if (event.key === 'ArrowUp') {
             this.active = (this.active === 0) ? this.suggestions.length - 1 : this.active-1;
         }
-        // Enter or tab keys
-        else if ((event.keyCode === 13 || event.keyCode === 9) && !event.shiftKey) {
+        // Enter key
+        else if ((event.key === 'Enter') && !event.shiftKey) {
             this.selectSuggestion(this.suggestions[this.active]);
         }
         // Escape key
-        else if (event.keyCode === 27) {
+        else if (event.key === 'Escape') {
             this.showSuggestions = false;
         }
     },
similarity index 94%
rename from resources/assets/js/vues/components/dropzone.js
rename to resources/js/vues/components/dropzone.js
index 751cca330020c8d3cf3ffeba6e70e84d33774779..1c045727f842686ebbcb690ced96307c56b1017f 100644 (file)
@@ -2,8 +2,8 @@ import DropZone from "dropzone";
 import { fadeOut } from "../../services/animations";
 
 const template = `
-    <div class="dropzone-container">
-        <div class="dz-message">{{placeholder}}</div>
+    <div class="dropzone-container text-center">
+        <button type="button" class="dz-message">{{placeholder}}</button>
     </div>
 `;
 
similarity index 98%
rename from resources/assets/js/vues/tag-manager.js
rename to resources/js/vues/tag-manager.js
index e0dab595ae79502d17655634691e210a799cca68..65233cbb676636bd7756ade1cd48b026d0572835 100644 (file)
@@ -1,7 +1,7 @@
 import draggable from 'vuedraggable';
 import autosuggest from './components/autosuggest';
 
-let data = {
+const data = {
     entityId: false,
     entityType: null,
     tags: [],
@@ -10,7 +10,7 @@ let data = {
 const components = {draggable, autosuggest};
 const directives = {};
 
-let methods = {
+const methods = {
 
     addEmptyTag() {
         this.tags.push({name: '', value: '', key: Math.random().toString(36).substring(7)});
index fd13b16aa883bbffb024b96575169eb68f7a158e..348eba3985de052b0d1dd47d16436ecd8a18f3df 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => 'تم إنشاء صفحة',
     'page_create_notification'    => 'تم إنشاء الصفحة بنجاح',
@@ -37,6 +35,14 @@ return [
     'book_sort'                   => 'تم سرد الكتاب',
     'book_sort_notification'      => 'تمت إعادة سرد الكتاب بنجاح',
 
+    // Bookshelves
+    'bookshelf_create'            => 'created Bookshelf',
+    'bookshelf_create_notification'    => 'Bookshelf Successfully Created',
+    'bookshelf_update'                 => 'updated bookshelf',
+    'bookshelf_update_notification'    => 'Bookshelf Successfully Updated',
+    'bookshelf_delete'                 => 'deleted bookshelf',
+    'bookshelf_delete_notification'    => 'Bookshelf Successfully Deleted',
+
     // Other
     'commented_on'                => 'تم التعليق',
 ];
index bad0910a28f20592d5fb836ab83f4f67cf4852f1..d9ed5cf27a91152e6b568eacb331762754c9ce37 100644 (file)
@@ -1,21 +1,15 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => 'البيانات المعطاة لا توافق سجلاتنا.',
     'throttle' => 'تجاوزت الحد الأقصى من المحاولات. الرجاء المحاولة مرة أخرى بعد :seconds seconds.',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => 'إنشاء حساب',
     'log_in' => 'تسجيل الدخول',
     'log_in_with' => 'تسجيل الدخول باستخدام :socialDriver',
@@ -27,11 +21,13 @@ return [
     'email' => 'البريد الإلكتروني',
     'password' => 'كلمة المرور',
     'password_confirm' => 'تأكيد كلمة المرور',
-    'password_hint' => 'يجب أن تكون أكثر من 5 حروف',
+    'password_hint' => 'يجب أن تكون أكثر من 7 حروف',
     'forgot_password' => 'نسيت كلمة المرور؟',
     'remember_me' => 'تذكرني',
     'ldap_email_hint' => 'الرجاء إدخال عنوان بريد إلكتروني لاستخدامه مع الحساب.',
     'create_account' => 'إنشاء حساب',
+    'already_have_account' => 'Already have an account?',
+    'dont_have_account' => 'Don\'t have an account?',
     'social_login' => 'تسجيل الدخول باستخدام حسابات التواصل الاجتماعي',
     'social_registration' => 'إنشاء حساب باستخدام حسابات التواصل الاجتماعي',
     'social_registration_text' => 'إنشاء حساب والدخول باستخدام خدمة أخرى.',
@@ -43,23 +39,18 @@ return [
     'register_success' => 'شكراً لإنشاء حسابكم! تم تسجيلكم ودخولكم للحساب الخاص بكم.',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => 'استعادة كلمة المرور',
     'reset_password_send_instructions' => 'أدخل بريدك الإلكتروني بالأسفل وسيتم إرسال رسالة برابط لاستعادة كلمة المرور.',
     'reset_password_send_button' => 'أرسل رابط الاستعادة',
     'reset_password_sent_success' => 'تم إرسال رابط استعادة كلمة المرور إلى :email.',
     'reset_password_success' => 'تمت استعادة كلمة المرور بنجاح.',
-
     'email_reset_subject' => 'استعد كلمة المرور الخاصة بتطبيق :appName',
     'email_reset_text' => 'تم إرسال هذه الرسالة بسبب تلقينا لطلب استعادة كلمة المرور الخاصة بحسابكم.',
     'email_reset_not_requested' => 'إذا لم يتم طلب استعادة كلمة المرور من قبلكم, فلا حاجة لاتخاذ أية خطوات.',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => 'تأكيد بريدكم الإلكتروني لتطبيق :appName',
     'email_confirm_greeting' => 'شكرا لانضمامكم إلى :appName!',
     'email_confirm_text' => 'الرجاء تأكيد بريدكم الإلكتروني بالضغط على الزر أدناه:',
@@ -73,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'الرجاء الضغط على الرابط المرسل إلى بريدكم الإلكتروني بعد تسجيلكم.',
     'email_not_confirmed_resend' => 'إذا لم يتم إيجاد الرسالة, بإمكانكم إعادة إرسال رسالة التأكيد عن طريق تعبئة النموذج أدناه.',
     'email_not_confirmed_resend_button' => 'إعادة إرسال رسالة التأكيد',
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
 ];
\ No newline at end of file
index 9c978b454301b584912d672c2fa1e0a59deeb58e..9505b2a9577d7198f1a34f686d9c12a93002f1e7 100644 (file)
@@ -1,31 +1,30 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => 'إلغاء',
     'confirm' => 'تأكيد',
     'back' => 'رجوع',
     'save' => 'حفظ',
     'continue' => 'استمرار',
     'select' => 'تحديد',
+    'toggle_all' => 'Toggle All',
     'more' => 'المزيد',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => 'الاسم',
     'description' => 'الوصف',
     'role' => 'الدور',
     'cover_image' => 'صورة الغلاف',
     'cover_image_description' => 'الصورة يجب أن تكون مقاربة لحجم 440×250 بكسل.',
     
-    /**
-     * Actions
-     */
+    // Actions
     'actions' => 'إجراءات',
     'view' => 'عرض',
+    'view_all' => 'View All',
     'create' => 'إنشاء',
     'update' => 'تحديث',
     'edit' => 'تعديل',
@@ -40,9 +39,16 @@ return [
     'remove' => 'إزالة',
     'add' => 'إضافة',
 
-    /**
-     * Misc
-     */
+    // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
+    'sort_name' => 'Name',
+    'sort_created_at' => 'Created Date',
+    'sort_updated_at' => 'Updated Date',
+
+    // Misc
     'deleted_user' => 'حذف مستخدم',
     'no_activity' => 'لا يوجد نشاط لعرضه',
     'no_items' => 'لا توجد عناصر متوفرة',
@@ -52,16 +58,19 @@ return [
     'details' => 'التفاصيل',
     'grid_view' => 'عرض شبكي',
     'list_view' => 'عرض منسدل',
+    'default' => 'Default',
+    'breadcrumb' => 'Breadcrumb',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => 'عرض الملف الشخصي',
     'edit_profile' => 'تعديل الملف الشخصي',
 
-    /**
-     * Email Content
-     */
+    // Layout tabs
+    'tab_info' => 'Info',
+    'tab_content' => 'Content',
+
+    // Email Content
     'email_action_help' => 'إذا واجهتكم مشكلة بضغط زر ":actionText" فبإمكانكم نسخ الرابط أدناه ولصقه بالمتصفح:',
     'email_rights' => 'جميع الحقوق محفوظة',
-];
\ No newline at end of file
+];
index f98558935e2159215d183305b04708f11abff6eb..aa3935bd900f5e7f4dba7054fd863a3fae08dbfb 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => 'تحديد صورة',
     'image_all' => 'الكل',
     'image_all_title' => 'عرض جميع الصور',
@@ -24,9 +25,7 @@ return [
     'image_delete_success' => 'تم حذف الصورة بنجاح',
     'image_upload_remove' => 'إزالة',
 
-    /**
-     * Code editor
-     */
+    // Code Editor
     'code_editor' => 'تعديل الشفرة',
     'code_language' => 'لغة الشفرة',
     'code_content' => 'محتويات الشفرة',
index 55099380db0e3d624c2269dbe5955368108c3645..9278c8cf37caee241dfa456457f1b1948046ce92 100644 (file)
@@ -1,14 +1,17 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => 'أنشئت مؤخراً',
     'recently_created_pages' => 'صفحات أنشئت مؤخراً',
     'recently_updated_pages' => 'صفحات حُدثت مؤخراً',
     'recently_created_chapters' => 'فصول أنشئت مؤخراً',
     'recently_created_books' => 'كتب أنشئت مؤخراً',
+    'recently_created_shelves' => 'Recently Created Shelves',
     'recently_update' => 'حُدثت مؤخراً',
     'recently_viewed' => 'عُرضت مؤخراً',
     'recent_activity' => 'نشاطات حديثة',
@@ -19,7 +22,7 @@ return [
     'meta_created_name' => 'أنشئ :timeLength بواسطة :user',
     'meta_updated' => 'مُحدث :timeLength',
     'meta_updated_name' => 'مُحدث :timeLength بواسطة :user',
-    'entity_select' => 'Entity Select', // جار البحث عن الترجمة الأنسب
+    'entity_select' => 'Entity Select',
     'images' => 'صور',
     'my_recent_drafts' => 'مسوداتي الحديثة',
     'my_recently_viewed' => 'ما عرضته مؤخراً',
@@ -31,17 +34,13 @@ return [
     'export_pdf' => 'ملف PDF',
     'export_text' => 'ملف نص عادي',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => 'الأذونات',
     'permissions_intro' => 'في حال التفعيل, ستتم تبدية هذه الأذونات على أذونات الأدوار.',
     'permissions_enable' => 'تفعيل الأذونات المخصصة',
     'permissions_save' => 'حفظ الأذونات',
 
-    /**
-     * Search //
-     */
+    // Search
     'search_results' => 'نتائج البحث',
     'search_total_results_found' => 'عدد النتائج :count|مجموع النتائج :count',
     'search_clear' => 'مسح البحث',
@@ -52,11 +51,13 @@ return [
     'search_content_type' => 'نوع المحتوى',
     'search_exact_matches' => 'نتائج مطابقة تماماً',
     'search_tags' => 'بحث الوسوم',
+    'search_options' => 'Options',
     'search_viewed_by_me' => 'تم استعراضها من قبلي',
     'search_not_viewed_by_me' => 'لم يتم استعراضها من قبلي',
     'search_permissions_set' => 'حزمة الأذونات',
     'search_created_by_me' => 'أنشئت بواسطتي',
     'search_updated_by_me' => 'حُدثت بواسطتي',
+    'search_date_options' => 'Date Options',
     'search_updated_before' => 'حدثت قبل',
     'search_updated_after' => 'حدثت بعد',
     'search_created_before' => 'أنشئت قبل',
@@ -64,9 +65,39 @@ return [
     'search_set_date' => 'تحديد التاريخ',
     'search_update' => 'تحديث البحث',
 
-    /**
-     * Books
-     */
+    // Shelves
+    'shelf' => 'Shelf',
+    'shelves' => 'Shelves',
+    'x_shelves' => ':count Shelf|:count Shelves',
+    'shelves_long' => 'Bookshelves',
+    'shelves_empty' => 'No shelves have been created',
+    'shelves_create' => 'Create New Shelf',
+    'shelves_popular' => 'Popular Shelves',
+    'shelves_new' => 'New Shelves',
+    'shelves_new_action' => 'New Shelf',
+    'shelves_popular_empty' => 'The most popular shelves will appear here.',
+    'shelves_new_empty' => 'The most recently created shelves will appear here.',
+    'shelves_save' => 'Save Shelf',
+    'shelves_books' => 'Books on this shelf',
+    'shelves_add_books' => 'Add books to this shelf',
+    'shelves_drag_books' => 'Drag books here to add them to this shelf',
+    'shelves_empty_contents' => 'This shelf has no books assigned to it',
+    'shelves_edit_and_assign' => 'Edit shelf to assign books',
+    'shelves_edit_named' => 'Edit Bookshelf :name',
+    'shelves_edit' => 'Edit Bookshelf',
+    'shelves_delete' => 'Delete Bookshelf',
+    'shelves_delete_named' => 'Delete Bookshelf :name',
+    'shelves_delete_explain' => "This will delete the bookshelf with the name ':name'. Contained books will not be deleted.",
+    'shelves_delete_confirmation' => 'Are you sure you want to delete this bookshelf?',
+    'shelves_permissions' => 'Bookshelf Permissions',
+    'shelves_permissions_updated' => 'Bookshelf Permissions Updated',
+    'shelves_permissions_active' => 'Bookshelf Permissions Active',
+    'shelves_copy_permissions_to_books' => 'Copy Permissions to Books',
+    'shelves_copy_permissions' => 'Copy Permissions',
+    'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this bookshelf to all books contained within. Before activating, ensure any changes to the permissions of this bookshelf have been saved.',
+    'shelves_copy_permission_success' => 'Bookshelf permissions copied to :count books',
+
+    // Books
     'book' => 'كتاب',
     'books' => 'كتب',
     'x_books' => ':count كتاب|:count كتب',
@@ -74,6 +105,7 @@ return [
     'books_popular' => 'كتب رائجة',
     'books_recent' => 'كتب حديثة',
     'books_new' => 'كتب جديدة',
+    'books_new_action' => 'New Book',
     'books_popular_empty' => 'الكتب الأكثر رواجاً ستظهر هنا.',
     'books_new_empty' => 'الكتب المنشأة مؤخراً ستظهر هنا.',
     'books_create' => 'إنشاء كتاب جديد',
@@ -89,7 +121,6 @@ return [
     'books_permissions_updated' => 'تم تحديث أذونات الكتاب',
     'books_empty_contents' => 'لم يتم إنشاء أي صفحات أو فصول لهذا الكتاب.',
     'books_empty_create_page' => 'إنشاء صفحة جديدة',
-    'books_empty_or' => 'أو',
     'books_empty_sort_current_book' => 'فرز الكتاب الحالي',
     'books_empty_add_chapter' => 'إضافة فصل',
     'books_permissions_active' => 'أذونات الكتاب مفعلة',
@@ -97,12 +128,15 @@ return [
     'books_navigation' => 'تصفح الكتاب',
     'books_sort' => 'فرز محتويات الكتاب',
     'books_sort_named' => 'فرز كتاب :bookName',
+    'books_sort_name' => 'Sort by Name',
+    'books_sort_created' => 'Sort by Created Date',
+    'books_sort_updated' => 'Sort by Updated Date',
+    'books_sort_chapters_first' => 'Chapters First',
+    'books_sort_chapters_last' => 'Chapters Last',
     'books_sort_show_other' => 'عرض كتب أخرى',
     'books_sort_save' => 'حفظ الترتيب الجديد',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => 'فصل',
     'chapters' => 'فصول',
     'x_chapters' => ':count فصل|:count فصول',
@@ -125,9 +159,7 @@ return [
     'chapters_permissions_success' => 'تم تحديث أذونات الفصل',
     'chapters_search_this' => 'البحث في هذا الفصل',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => 'صفحة',
     'pages' => 'صفحات',
     'x_pages' => ':count صفحة|:count صفحات',
@@ -144,7 +176,7 @@ return [
     'pages_delete_confirm' => 'تأكيد حذف الصفحة؟',
     'pages_delete_draft_confirm' => 'تأكيد حذف المسودة؟',
     'pages_editing_named' => ':pageName قيد التعديل',
-    'pages_edit_toggle_header' => 'إظهار / إخفاء الترويسة',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'حفظ المسودة',
     'pages_edit_draft' => 'تعديل مسودة الصفحة',
     'pages_editing_draft' => 'المسودة قيد التعديل',
@@ -161,7 +193,7 @@ return [
     'pages_md_editor' => 'المحرر',
     'pages_md_preview' => 'معاينة',
     'pages_md_insert_image' => 'إدخال صورة',
-    'pages_md_insert_link' => 'Insert Entity Link', // جار البحث عن الترجمة الأنسب
+    'pages_md_insert_link' => 'Insert Entity Link',
     'pages_md_insert_drawing' => 'إدخال رسمة',
     'pages_not_in_chapter' => 'صفحة ليست في فصل',
     'pages_move' => 'نقل الصفحة',
@@ -178,6 +210,8 @@ return [
     'pages_revisions_created_by' => 'أنشئ بواسطة',
     'pages_revisions_date' => 'تاريخ المراجعة',
     'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
     'pages_revisions_changelog' => 'سجل التعديل',
     'pages_revisions_changes' => 'التعديلات',
     'pages_revisions_current' => 'النسخة الحالية',
@@ -196,21 +230,24 @@ return [
         'start_b' => ':userName بدأ بتعديل هذه الصفحة',
         'time_a' => 'منذ أن تم تحديث هذه الصفحة',
         'time_b' => 'في آخر :minCount دقيقة/دقائق',
-        'message' => ':start :time. Take care not to overwrite each other\'s updates!', // جار البحث عن الترجمة الأنسب
+        'message' => ':start :time. Take care not to overwrite each other\'s updates!',
     ],
     'pages_draft_discarded' => 'تم التخلص من المسودة. تم تحديث المحرر بمحتوى الصفحة الحالي',
+    'pages_specific' => 'Specific Page',
+    'pages_is_template' => 'Page Template',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => 'وسوم الصفحة',
     'chapter_tags' => 'وسوم الفصل',
     'book_tags' => 'وسوم الكتاب',
+    'shelf_tags' => 'Shelf Tags',
     'tag' => 'وسم',
     'tags' =>  'وسوم',
+    'tag_name' =>  'Tag Name',
     'tag_value' => 'قيمة الوسم (اختياري)',
     'tags_explain' => "إضافة الوسوم تساعد بترتيب وتقسيم المحتوى. \n من الممكن وضع قيمة لكل وسم لترتيب أفضل وأدق.",
     'tags_add' => 'إضافة وسم آخر',
+    'tags_remove' => 'Remove this tag',
     'attachments' => 'المرفقات',
     'attachments_explain' => 'ارفع بعض الملفات أو أرفق بعض الروابط لعرضها بصفحتك. ستكون الملفات والروابط معروضة في الشريط الجانبي للصفحة.',
     'attachments_explain_instant_save' => 'سيتم حفظ التغييرات هنا بلحظتها',
@@ -224,7 +261,7 @@ return [
     'attachments_explain_link' => 'بالإمكان إرفاق رابط في حال عدم تفضيل رفع ملف. قد يكون الرابط لصفحة أخرى أو لملف في أحد خدمات التخزين السحابي.',
     'attachments_link_name' => 'اسم الرابط',
     'attachment_link' => 'رابط المرفق',
-    'attachments_link_url' => 'Link to file', // جار البحث عن الترجمة الأنسب - هل المقصود الربط بالملف أو رابط يشير إلى ملف
+    'attachments_link_url' => 'Link to file',
     'attachments_link_url_hint' => 'رابط الموقع أو الملف',
     'attach' => 'Attach',
     'attachments_edit_file' => 'تعديل الملف',
@@ -236,19 +273,22 @@ return [
     'attachments_file_uploaded' => 'تم رفع الملف بنجاح',
     'attachments_file_updated' => 'تم تحديث الملف بنجاح',
     'attachments_link_attached' => 'تم إرفاق الرابط بالصفحة بنجاح',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
-    /**
-     * Profile View
-     */
-    'profile_user_for_x' => 'User for :time', // جار البحث عن الترجمة الأنسب
+    // Profile View
+    'profile_user_for_x' => 'User for :time',
     'profile_created_content' => 'المحتوى المنشأ',
     'profile_not_created_pages' => 'لم يتم إنشاء أي صفحات بواسطة :userName',
     'profile_not_created_chapters' => 'لم يتم إنشاء أي فصول بواسطة :userName',
     'profile_not_created_books' => 'لم يتم إنشاء أي كتب بواسطة :userName',
+    'profile_not_created_shelves' => ':userName has not created any shelves',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => 'تعليق',
     'comments' => 'تعليقات',
     'comment_add' => 'إضافة تعليق',
@@ -265,4 +305,10 @@ return [
     'comment_updated_success' => 'تم تحديث التعليق',
     'comment_delete_confirm' => 'تأكيد حذف التعليق؟',
     'comment_in_reply_to' => 'رداً على :commentId',
-];
+
+    // Revision
+    'revision_delete_confirm' => 'Are you sure you want to delete this revision?',
+    'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
+    'revision_delete_success' => 'Revision deleted',
+    'revision_cannot_delete_latest' => 'Cannot delete the latest revision.'
+];
\ No newline at end of file
index 6be77a1b48f556b3f65e5dca02a04afa8e8cbca7..dd42338b5c0d843178c606c564ece024527d7174 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => 'لم يؤذن لك بالدخول للصفحة المطلوبة.',
     'permissionJson' => 'لم يؤذن لك بعمل الإجراء المطلوب.',
@@ -27,8 +25,9 @@ return [
     'social_account_already_used_existing' => 'حساب :socialAccount مستخدَم من قبل مستخدم آخر.',
     'social_account_not_used' => 'حساب :socialAccount غير مرتبط بأي مستخدم. الرجاء ربطه من خلال إعدادات ملفكم. ',
     'social_account_register_instructions' => 'إذا لم يكن لديكم حساب فيمكنكم التجسيل باستخدام خيار :socialAccount.',
-    'social_driver_not_found' => 'Social driver not found', // جار البحث عن الترجمة الأنسب
-    'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.', // جار البحث عن الترجمة الأنسب
+    'social_driver_not_found' => 'Social driver not found',
+    'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => 'لا يمكن الرفع إلى مسار :filePath. الرجاء التأكد من قابلية الكتابة إلى الخادم.',
@@ -41,7 +40,7 @@ return [
     'file_upload_timeout' => 'انتهت عملية تحميل الملف.',
 
     // Attachments
-    'attachment_page_mismatch' => 'Page mismatch during attachment update', // جار البحث عن الترجمة الأنسب
+    'attachment_page_mismatch' => 'Page mismatch during attachment update',
     'attachment_not_found' => 'لم يتم العثور على المرفق',
 
     // Pages
@@ -49,7 +48,8 @@ return [
     'page_custom_home_deletion' => 'لا يمكن حذف الصفحة إذا كانت محددة كصفحة رئيسية',
 
     // Entities
-    'entity_not_found' => 'Entity not found', // جار البحث عن الترجمة الأنسب
+    'entity_not_found' => 'Entity not found',
+    'bookshelf_not_found' => 'Bookshelf not found',
     'book_not_found' => 'لم يتم العثور على الكتاب',
     'page_not_found' => 'لم يتم العثور على الصفحة',
     'chapter_not_found' => 'لم يتم العثور على الفصل',
@@ -65,6 +65,7 @@ return [
     'role_cannot_be_edited' => 'لا يمكن تعديل هذا الدور',
     'role_system_cannot_be_deleted' => 'هذا الدور خاص بالنظام ولا يمكن حذفه',
     'role_registration_default_cannot_delete' => 'لا يمكن حذف الدور إذا كان مسجل كالدور الأساسي بعد تسجيل الحساب',
+    'role_cannot_remove_only_admin' => 'This user is the only user assigned to the administrator role. Assign the administrator role to another user before attempting to remove it here.',
 
     // Comments
     'comment_list' => 'حصل خطأ خلال جلب التعليقات.',
@@ -80,4 +81,5 @@ return [
     'error_occurred' => 'حدث خطأ',
     'app_down' => ':appName لا يعمل حالياً',
     'back_soon' => 'سيعود للعمل قريباً.',
+
 ];
index 9a1276a8d4abbc10cc60082260f171b8c7311b33..4ba36f6df445fc944645764f5f43c5df186aabcf 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; السابق',
     'next'     => 'التالي &raquo;',
 
index 6af597f7951d358179b67a2508543cacc7f8fc3a..23a8a7e7429a465b38a13c7cb1c05ee5dbbb7861 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => 'يجب أن تتكون كلمة المرور من ستة أحرف على الأقل وأن تطابق التأكيد.',
     'user' => "لم يتم العثور على مستخدم بعنوان البريد الإلكتروني المعطى.",
     'token' => 'رابط تجديد كلمة المرور غير صحيح.',
index 850776a59dbaf4a505767abe9f46ccbd7891c5ea..d867e30b51301e6f3f247a3986991830248ffd46 100755 (executable)
@@ -1,80 +1,77 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
-
+    // Common Messages
     'settings' => 'الإعدادات',
     'settings_save' => 'حفظ الإعدادات',
     'settings_save_success' => 'تم حفظ الإعدادات',
 
-    /**
-     * App settings
-     */
-
-    'app_settings' => 'إعدادات التطبيق',
+    // App Settings
+    'app_customization' => 'Customization',
+    'app_features_security' => 'Features & Security',
     'app_name' => 'اسم التطبيق',
     'app_name_desc' => 'سيتم عرض هذا الاسم في الترويسة وفي أي رسالة بريد إلكتروني.',
     'app_name_header' => 'عرض اسم التطبيق في الترويسة؟',
+    'app_public_access' => 'Public Access',
+    'app_public_access_desc' => 'Enabling this option will allow visitors, that are not logged-in, to access content in your BookStack instance.',
+    'app_public_access_desc_guest' => 'Access for public visitors can be controlled through the "Guest" user.',
+    'app_public_access_toggle' => 'Allow public access',
     'app_public_viewing' => 'السماح بالعرض على العامة؟',
     'app_secure_images' => 'تفعيل حماية أكبر لرفع الصور؟',
+    'app_secure_images_toggle' => 'Enable higher security image uploads',
     'app_secure_images_desc' => 'لتحسين أداء النظام, ستكون جميع الصور متاحة للعامة. هذا الخيار يضيف سلسلة من الحروف والأرقام العشوائية صعبة التخمين إلى رابط الصورة. الرجاء التأكد من تعطيل فهرسة المسارات لمنع الوصول السهل.',
     'app_editor' => 'محرر الصفحة',
     'app_editor_desc' => 'الرجاء اختيار محرر النص الذي سيستخدم من قبل جميع المستخدمين لتحرير الصفحات.',
-    'app_custom_html' => 'Custom HTML head content', // جار البحث عن الترجمة الأنسب
-    'app_custom_html_desc' => 'Any content added here will be inserted into the bottom of the <head> section of every page. This is handy for overriding styles or adding analytics code.', // جار البحث عن الترجمة الأنسب
+    'app_custom_html' => 'Custom HTML head content',
+    'app_custom_html_desc' => 'Any content added here will be inserted into the bottom of the <head> section of every page. This is handy for overriding styles or adding analytics code.',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'شعار التطبيق',
     'app_logo_desc' => 'يجب أن تكون الصورة بارتفاع 43 بكسل. <br>سيتم تصغير الصور الأكبر من ذلك.',
     'app_primary_color' => 'اللون الأساسي للتطبيق',
     'app_primary_color_desc' => 'يجب أن تكون القيمة من نوع hex. <br>اترك الخانة فارغة للرجوع للون الافتراضي.',
     'app_homepage' => 'الصفحة الرئيسية للتطبيق',
     'app_homepage_desc' => 'الرجاء اختيار صفحة لتصبح الصفحة الرئيسية بدل من الافتراضية. سيتم تجاهل جميع الأذونات الخاصة بالصفحة المختارة.',
-    'app_homepage_default' => 'شكل الصفحة الافتراضية المختارة',
-    'app_homepage_books' => 'أو من الممكن اختيار صفحة الكتب كصفحة رئيسية. سيتم استبدالها بأي صفحة سابقة تم اختيارها كصفحة رئيسية.',
+    'app_homepage_select' => 'Select a page',
     'app_disable_comments' => 'تعطيل التعليقات',
+    'app_disable_comments_toggle' => 'Disable comments',
     'app_disable_comments_desc' => 'تعطيل التعليقات على جميع الصفحات داخل التطبيق. التعليقات الموجودة من الأصل لن تكون ظاهرة.',
 
-    /**
-     * Registration settings
-     */
-
+    // Registration Settings
     'reg_settings' => 'إعدادات التسجيل',
-    'reg_allow' => 'السماح بالتسجيل؟',
+    'reg_enable' => 'Enable Registration',
+    'reg_enable_toggle' => 'Enable registration',
+    'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
     'reg_default_role' => 'دور المستخدم الأساسي بعد التسجيل',
-    'reg_confirm_email' => 'فرض التأكيد عن طريق البريد الإلكتروني؟',
+    'reg_email_confirmation' => 'Email Confirmation',
+    'reg_email_confirmation_toggle' => 'Require email confirmation',
     'reg_confirm_email_desc' => 'إذا تم استخدام قيود للمجال سيصبح التأكيد عن طريق البريد الإلكتروني إلزامي وسيتم تجاهل القيمة أسفله.',
     'reg_confirm_restrict_domain' => 'تقييد التسجيل على مجال محدد',
-    'reg_confirm_restrict_domain_desc' => 'Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application. <br> Note that users will be able to change their email addresses after successful registration.', // جار البحث عن الترجمة الأنسب
+    'reg_confirm_restrict_domain_desc' => 'Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application. <br> Note that users will be able to change their email addresses after successful registration.',
     'reg_confirm_restrict_domain_placeholder' => 'لم يتم اختيار أي قيود',
 
-    /**
-     * Maintenance settings
-     */
-
+    // Maintenance settings
     'maint' => 'الصيانة',
     'maint_image_cleanup' => 'تنظيف الصور',
-    'maint_image_cleanup_desc' => "Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.", // جار البحث عن الترجمة الأنسب
+    'maint_image_cleanup_desc' => "Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.",
     'maint_image_cleanup_ignore_revisions' => 'تجاهل الصور في المراجعات',
     'maint_image_cleanup_run' => 'بدء التنظيف',
     'maint_image_cleanup_warning' => 'يوجد عدد :count من الصور المحتمل عدم استخدامها. تأكيد حذف الصور؟',
     'maint_image_cleanup_success' => 'تم إيجاد وحذف عدد :count من الصور المحتمل عدم استخدامها!',
     'maint_image_cleanup_nothing_found' => 'لم يتم حذف أي شيء لعدم وجود أي صور غير مسمتخدمة',
 
-    /**
-     * Role settings
-     */
-
+    // Role Settings
     'roles' => 'الأدوار',
     'role_user_roles' => 'أدوار المستخدمين',
     'role_create' => 'إنشاء دور جديد',
     'role_create_success' => 'تم إنشاء الدور بنجاح',
     'role_delete' => 'حذف الدور',
     'role_delete_confirm' => 'سيتم حذف الدور المسمى \':roleName\'.',
-    'role_delete_users_assigned' => 'This role has :userCount users assigned to it. If you would like to migrate the users from this role select a new role below.', // جار البحث عن الترجمة الأنسب
+    'role_delete_users_assigned' => 'This role has :userCount users assigned to it. If you would like to migrate the users from this role select a new role below.',
     'role_delete_no_migration' => "لا تقم بترجيل المستخدمين",
     'role_delete_sure' => 'تأكيد حذف الدور؟',
     'role_delete_success' => 'تم حذف الدور بنجاح',
@@ -82,33 +79,41 @@ return [
     'role_details' => 'تفاصيل الدور',
     'role_name' => 'اسم الدور',
     'role_desc' => 'وصف مختصر للدور',
-    'role_external_auth_id' => 'External Authentication IDs', // جار البحث عن الترجمة الأنسب
+    'role_external_auth_id' => 'External Authentication IDs',
     'role_system' => 'أذونات النظام',
     'role_manage_users' => 'إدارة المستخدمين',
     'role_manage_roles' => 'إدارة الأدوار وأذوناتها',
     'role_manage_entity_permissions' => 'إدارة جميع أذونات الكتب والفصول والصفحات',
     'role_manage_own_entity_permissions' => 'إدارة الأذونات الخاصة بكتابك أو فصلك أو صفحاتك',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'إدارة إعدادات التطبيق',
-    'role_asset' => 'Asset Permissions', // جار البحث عن الترجمة الأنسب
-    'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', // جار البحث عن الترجمة الأنسب
+    'role_asset' => 'Asset Permissions',
+    'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
+    'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.',
     'role_all' => 'الكل',
     'role_own' => 'Own',
-    'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', // جار البحث عن الترجمة الأنسب
+    'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
     'role_save' => 'حفظ الدور',
     'role_update_success' => 'تم تحديث الدور بنجاح',
     'role_users' => 'مستخدمون داخل هذا الدور',
     'role_users_none' => 'لم يتم تعيين أي مستخدمين لهذا الدور',
 
-    /**
-     * Users
-     */
-
+    // Users
     'users' => 'المستخدمون',
     'user_profile' => 'ملف المستخدم',
     'users_add_new' => 'إضافة مستخدم جديد',
     'users_search' => 'بحث عن مستخدم',
+    'users_details' => 'User Details',
+    'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.',
+    'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.',
     'users_role' => 'أدوار المستخدمين',
-    'users_external_auth_id' => 'External Authentication ID', // جار البحث عن الترجمة الأنسب
+    'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
+    'users_password' => 'User Password',
+    'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 6 characters long.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
+    'users_external_auth_id' => 'External Authentication ID',
+    'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your LDAP system.',
     'users_password_warning' => 'الرجاء ملئ الحقل أدناه فقط في حال أردتم تغيير كلمة المرور:',
     'users_system_public' => 'هذا المستخدم يمثل أي ضيف يقوم بزيارة شيء يخصك. لا يمكن استخدامه لتسجيل الدخول ولكن يتم تعيينه تلقائياً.',
     'users_delete' => 'حذف المستخدم',
@@ -122,10 +127,40 @@ return [
     'users_avatar' => 'صورة المستخدم',
     'users_avatar_desc' => 'يجب أن تكون الصورة مربعة ومقاربة لحجم 256 بكسل',
     'users_preferred_language' => 'اللغة المفضلة',
+    'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
     'users_social_accounts' => 'الحسابات الاجتماعية',
-    'users_social_accounts_info' => 'Here you can connect your other accounts for quicker and easier login. Disconnecting an account here does not previously authorized access. Revoke access from your profile settings on the connected social account.', // جار البحث عن الترجمة الأنسب
+    'users_social_accounts_info' => 'Here you can connect your other accounts for quicker and easier login. Disconnecting an account here does not previously authorized access. Revoke access from your profile settings on the connected social account.',
     'users_social_connect' => 'ربط الحساب',
     'users_social_disconnect' => 'فصل الحساب',
     'users_social_connected' => 'تم ربط حساب :socialAccount بملفك بنجاح.',
     'users_social_disconnected' => 'تم فصل حساب :socialAccount من ملفك بنجاح.',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index 47035a97d55503c64e4ebf7663c5762162eacc0c..7d6d13e817713fed9832856a4bc57458a054edf0 100644 (file)
@@ -1,25 +1,20 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => 'يجب الموافقة على :attribute.',
     'active_url'           => ':attribute ليس رابط صالح.',
     'after'                => 'يجب أن يكون التاريخ :attribute بعد :date.',
     'alpha'                => 'يجب أن يقتصر :attribute على الحروف فقط.',
     'alpha_dash'           => 'يجب أن يقتصر :attribute على حروف أو أرقام أو شرطات فقط.',
     'alpha_num'            => 'يجب أن يقتصر :attribute على الحروف والأرقام فقط.',
-    'array'                => 'The :attribute must be an array.', // جار البحث عن الترجمة الأنسب
+    'array'                => 'The :attribute must be an array.',
     'before'               => 'يجب أن يكون التاريخ :attribute قبل :date.',
     'between'              => [
         'numeric' => 'يجب أن يكون :attribute بين :min و :max.',
@@ -27,7 +22,7 @@ return [
         'string'  => 'يجب أن يكون :attribute بين :min و :max حرف / حروف.',
         'array'   => 'يجب أن يكون :attribute بين :min و :max عنصر / عناصر.',
     ],
-    'boolean'              => 'The :attribute field must be true or false.', // جار البحث عن الترجمة الأنسب
+    'boolean'              => 'The :attribute field must be true or false.',
     'confirmed'            => ':attribute غير مطابق.',
     'date'                 => ':attribute ليس تاريخ صالح.',
     'date_format'          => ':attribute لا يطابق الصيغة :format.',
@@ -35,12 +30,41 @@ return [
     'digits'               => 'يجب أن يكون :attribute بعدد :digits خانات.',
     'digits_between'       => 'يجب أن يكون :attribute بعدد خانات بين :min و :max.',
     'email'                => 'يجب أن يكون :attribute عنوان بريد إلكتروني صالح.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => 'حقل :attribute مطلوب.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => ':attribute المحدد غير صالح.',
     'image'                => 'يجب أن يكون :attribute صورة.',
+    'image_extension'      => 'The :attribute must have a valid & supported image extension.',
     'in'                   => ':attribute المحدد غير صالح.',
     'integer'              => 'يجب أن يكون :attribute عدد صحيح.',
     'ip'                   => 'يجب أن يكون :attribute عنوان IP صالح.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => 'يجب ألا يكون :attribute أكبر من :max.',
         'file'    => 'يجب ألا يكون :attribute أكبر من :max كيلو بايت.',
@@ -54,7 +78,9 @@ return [
         'string'  => 'يجب أن يكون :attribute على الأقل :min حرف / حروف.',
         'array'   => 'يجب أن يحتوي :attribute على :min عنصر / عناصر كحد أدنى.',
     ],
+    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => ':attribute المحدد غير صالح.',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => 'يجب أن يكون :attribute رقم.',
     'regex'                => 'صيغة :attribute غير صالحة.',
     'required'             => 'حقل :attribute مطلوب.',
@@ -70,39 +96,19 @@ return [
         'string'  => 'يجب أن يكون :attribute بعدد :size حرف / حروف.',
         'array'   => 'يجب أن يحتوي :attribute على :size عنصر / عناصر.',
     ],
-    'string'               => 'The :attribute must be a string.', // جار البحث عن الترجمة الأنسب
+    'string'               => 'The :attribute must be a string.',
     'timezone'             => 'يجب أن تكون :attribute منطقة صالحة.',
     'unique'               => 'تم حجز :attribute من قبل.',
     'url'                  => 'صيغة :attribute غير صالحة.',
+    'uploaded'             => 'The file could not be uploaded. The server may not accept files of this size.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => 'يجب تأكيد كلمة المرور',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
diff --git a/resources/lang/check.php b/resources/lang/check.php
deleted file mode 100755 (executable)
index 92a7b1e..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-#!/usr/bin/env php
-<?php
-
-/**
- * Compares translation files to find missing and redundant content.
- */
-
-$args = array_slice($argv, 1);
-
-if (count($args) === 0) {
-    errorOut("Please provide a language code as the first argument (./check.php fr)");
-}
-
-
-// Get content from files
-$lang = formatLang($args[0]);
-$enContent = loadLang('en');
-$langContent = loadLang($lang);
-
-if (count($langContent) === 0) {
-    errorOut("No language content found for '{$lang}'");
-}
-
-info("Checking '{$lang}' translation content against 'en'");
-
-// Track missing lang strings
-$missingLangStrings = [];
-foreach ($enContent as $enKey => $enStr) {
-    if (strpos($enKey, 'settings.language_select.') === 0) {
-        unset($langContent[$enKey]);
-        continue;
-    }
-    if (!isset($langContent[$enKey])) {
-        $missingLangStrings[$enKey] = $enStr;
-        continue;
-    }
-    unset($langContent[$enKey]);
-}
-
-if (count($missingLangStrings) > 0) {
-    info("\n========================");
-    info("Missing language content");
-    info("========================");
-    outputFlatArray($missingLangStrings, $lang);
-}
-
-if (count($langContent) > 0) {
-    info("\n==========================");
-    info("Redundant language content");
-    info("==========================");
-    outputFlatArray($langContent, $lang);
-}
-
-function outputFlatArray($arr, $lang) {
-    $grouped = [];
-    foreach ($arr as $key => $val) {
-        $explodedKey = explode('.', $key);
-        $group = $explodedKey[0];
-        $path = implode('.', array_slice($explodedKey, 1));
-        if (!isset($grouped[$group])) $grouped[$group] = [];
-        $grouped[$group][$path] = $val;
-    }
-    foreach ($grouped as $filename => $arr) {
-        echo "\e[36m" . $lang . '/' . $filename . ".php\e[0m\n";
-        echo json_encode($arr, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE) . "\n";
-    }
-}
-
-function formatLang($lang) {
-    $langParts = explode('_', strtoupper($lang));
-    $langParts[0] = strtolower($langParts[0]);
-    return implode('_', $langParts);
-}
-
-function loadLang(string $lang) {
-    $dir = __DIR__ . "/{$lang}";
-    if (!file_exists($dir)) {
-       errorOut("Expected directory '{$dir}' does not exist");
-    }
-    $files = scandir($dir);
-    $data = [];
-    foreach ($files as $file) {
-        if (substr($file, -4) !== '.php') continue;
-        $fileData = include ($dir . '/' . $file);
-        $name = substr($file, 0, -4);
-        $data[$name] = $fileData;
-    }
-    return flattenArray($data);
-}
-
-function flattenArray(array $arr) {
-    $data = [];
-    foreach ($arr as $key => $arrItem) {
-        if (!is_array($arrItem)) {
-            $data[$key] = $arrItem;
-            continue;
-        }
-
-        $toUse = flattenArray($arrItem);
-        foreach ($toUse as $innerKey => $item) {
-            $data[$key . '.' . $innerKey] = $item;
-        }
-    }
-    return $data;
-}
-
-function info($text) {
-    echo "\e[34m" . $text . "\e[0m\n";
-}
-
-function errorOut($text) {
-    echo "\e[31m" . $text . "\e[0m\n";
-    exit(1);
-}
\ No newline at end of file
index 69d6f0b97946dd4c397d98bdabf25a6b9a4320b4..fbda0150d72688d446387174d31430b934b9be2f 100644 (file)
@@ -21,11 +21,13 @@ return [
     'email' => 'Email',
     'password' => 'Heslo',
     'password_confirm' => 'Potvrdit heslo',
-    'password_hint' => 'Musí mít víc než 5 znaků',
+    'password_hint' => 'Musí mít víc než 7 znaků',
     'forgot_password' => 'Zapomněli jste heslo?',
     'remember_me' => 'Neodhlašovat',
     'ldap_email_hint' => 'Zadejte email, který chcete přiřadit k tomuto účtu.',
     'create_account' => 'Vytvořit účet',
+    'already_have_account' => 'Already have an account?',
+    'dont_have_account' => 'Don\'t have an account?',
     'social_login' => 'Přihlášení přes sociální sítě',
     'social_registration' => 'Registrace přes sociální sítě',
     'social_registration_text' => 'Registrovat a přihlásit se přes jinou službu',
@@ -62,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Klikněte na odkaz v emailu který jsme vám zaslali ihned po registraci.',
     'email_not_confirmed_resend' => 'Pokud nemůžete nalézt email v příchozí poště, můžete si jej nechat poslat znovu pomocí formuláře níže.',
     'email_not_confirmed_resend_button' => 'Znovu poslat email pro potvrzení emailové adresy',
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
 ];
\ No newline at end of file
index b06e587d65cbfa834dfbd0b8bfbc82b8d359a78d..cad26410c1df09beea9d34123670e29517143866 100644 (file)
@@ -11,6 +11,7 @@ return [
     'save' => 'Uložit',
     'continue' => 'Pokračovat',
     'select' => 'Zvolit',
+    'toggle_all' => 'Toggle All',
     'more' => 'Více',
 
     // Form Labels
@@ -23,6 +24,7 @@ return [
     // Actions
     'actions' => 'Akce',
     'view' => 'Pohled',
+    'view_all' => 'View All',
     'create' => 'Vytvořit',
     'update' => 'Aktualizovat',
     'edit' => 'Upravit',
@@ -37,6 +39,15 @@ return [
     'remove' => 'Odstranit',
     'add' => 'Přidat',
 
+    // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
+    'sort_name' => 'Name',
+    'sort_created_at' => 'Created Date',
+    'sort_updated_at' => 'Updated Date',
+
     // Misc
     'deleted_user' => 'Smazaný uživatel',
     'no_activity' => 'Žádná aktivita k zobrazení',
@@ -48,12 +59,18 @@ return [
     'grid_view' => 'Zobrazit dlaždice',
     'list_view' => 'Zobrazit seznam',
     'default' => 'Výchozí',
+    'breadcrumb' => 'Breadcrumb',
 
     // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => 'Ukázat profil',
     'edit_profile' => 'Upravit profil',
 
+    // Layout tabs
+    'tab_info' => 'Info',
+    'tab_content' => 'Content',
+
     // Email Content
     'email_action_help' => 'Pokud se vám nedaří kliknout na tlačítko ":actionText", zkopírujte odkaz níže přímo do webového prohlížeče:',
     'email_rights' => 'Všechna práva vyhrazena',
-];
\ No newline at end of file
+];
index dbf34850f402d90d04cf41fed7b94659a31652bd..579d49127b86f2a312f796aa806775b38c184920 100644 (file)
@@ -11,6 +11,7 @@ return [
     'recently_updated_pages' => 'Nedávno aktualizované stránky',
     'recently_created_chapters' => 'Nedávno vytvořené kapitoly',
     'recently_created_books' => 'Nedávno vytvořené knihy',
+    'recently_created_shelves' => 'Recently Created Shelves',
     'recently_update' => 'Nedávno aktualizované',
     'recently_viewed' => 'Nedávno prohlížené',
     'recent_activity' => 'Nedávné činnosti',
@@ -67,11 +68,13 @@ return [
     // Shelves
     'shelf' => 'Knihovna',
     'shelves' => 'Knihovny',
+    'x_shelves' => ':count Shelf|:count Shelves',
     'shelves_long' => 'Knihovny',
     'shelves_empty' => 'Žádné knihovny nebyly vytvořeny',
     'shelves_create' => 'Vytvořit novou knihovnu',
     'shelves_popular' => 'Populární knihovny',
     'shelves_new' => 'Nové knihovny',
+    'shelves_new_action' => 'New Shelf',
     'shelves_popular_empty' => 'Nejpopulárnější knihovny se objeví zde.',
     'shelves_new_empty' => 'Nejnovější knihovny se objeví zde.',
     'shelves_save' => 'Uložit knihovnu',
@@ -102,6 +105,7 @@ return [
     'books_popular' => 'Populární knihy',
     'books_recent' => 'Nedávné knihy',
     'books_new' => 'Nové knihy',
+    'books_new_action' => 'New Book',
     'books_popular_empty' => 'Zde budou zobrazeny nejpopulárnější knihy.',
     'books_new_empty' => 'Zde budou zobrazeny nově vytvořené knihy.',
     'books_create' => 'Vytvořit novou knihu',
@@ -117,7 +121,6 @@ return [
     'books_permissions_updated' => 'Práva knihy upravena',
     'books_empty_contents' => 'V této knize nebyly vytvořeny žádné stránky ani kapitoly.',
     'books_empty_create_page' => 'Vytvořit novou stránku',
-    'books_empty_or' => 'nebo',
     'books_empty_sort_current_book' => 'Seřadit tuto knihu',
     'books_empty_add_chapter' => 'Přidat kapitolu',
     'books_permissions_active' => 'Účinná práva knihy',
@@ -125,6 +128,11 @@ return [
     'books_navigation' => 'Obsah knihy',
     'books_sort' => 'Seřadit obsah knihy',
     'books_sort_named' => 'Seřadit knihu :bookName',
+    'books_sort_name' => 'Sort by Name',
+    'books_sort_created' => 'Sort by Created Date',
+    'books_sort_updated' => 'Sort by Updated Date',
+    'books_sort_chapters_first' => 'Chapters First',
+    'books_sort_chapters_last' => 'Chapters Last',
     'books_sort_show_other' => 'Ukázat ostatní knihy',
     'books_sort_save' => 'Uložit nové pořadí',
 
@@ -168,7 +176,7 @@ return [
     'pages_delete_confirm' => 'Opravdu chcete tuto stránku smazat?',
     'pages_delete_draft_confirm' => 'Opravdu chcete tento koncept stránky smazat?',
     'pages_editing_named' => 'Úpravy stránky :pageName',
-    'pages_edit_toggle_header' => 'Ukázat hlavičku',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'Uložit koncept',
     'pages_edit_draft' => 'Upravit koncept stránky',
     'pages_editing_draft' => 'Úpravy konceptu',
@@ -202,6 +210,8 @@ return [
     'pages_revisions_created_by' => 'Vytvořeno uživatelem',
     'pages_revisions_date' => 'Datum revize',
     'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
     'pages_revisions_changelog' => 'Komentáře změn',
     'pages_revisions_changes' => 'Změny',
     'pages_revisions_current' => 'Aktuální verze',
@@ -224,6 +234,7 @@ return [
     ],
     'pages_draft_discarded' => 'Koncept zahozen. Editor nyní obsahuje aktuální verzi stránky.',
     'pages_specific' => 'Konkrétní stránka',
+    'pages_is_template' => 'Page Template',
 
     // Editor Sidebar
     'page_tags' => 'Štítky stránky',
@@ -232,9 +243,11 @@ return [
     'shelf_tags' => 'Štítky knihovny',
     'tag' => 'Štítek',
     'tags' =>  'Štítky',
+    'tag_name' =>  'Tag Name',
     'tag_value' => 'Hodnota Štítku (volitelné)',
     'tags_explain' => "Přidejte si štítky pro lepší kategorizaci knih. \n Štítky mohou nést i hodnotu pro detailnější klasifikaci.",
     'tags_add' => 'Přidat další štítek',
+    'tags_remove' => 'Remove this tag',
     'attachments' => 'Přílohy',
     'attachments_explain' => 'Nahrajte soubory nebo připojte odkazy, které se zobrazí na stránce. Budou k nalezení v postranní liště.',
     'attachments_explain_instant_save' => 'Změny zde provedené se okamžitě ukládají.',
@@ -260,6 +273,12 @@ return [
     'attachments_file_uploaded' => 'Soubor byl úspěšně nahrán',
     'attachments_file_updated' => 'Soubor byl úspěšně aktualizován',
     'attachments_link_attached' => 'Odkaz úspěšně přiložen ke stránce',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
     // Profile View
     'profile_user_for_x' => 'Uživatelem již :time',
@@ -267,6 +286,7 @@ return [
     'profile_not_created_pages' => ':userName nevytvořil/a žádný obsah',
     'profile_not_created_chapters' => ':userName nevytvořil/a žádné kapitoly',
     'profile_not_created_books' => ':userName nevytvořil/a žádné knihy',
+    'profile_not_created_shelves' => ':userName has not created any shelves',
 
     // Comments
     'comment' => 'Komentář',
@@ -288,7 +308,7 @@ return [
 
     // Revision
     'revision_delete_confirm' => 'Opravdu chcete smazat tuto revizi?',
+    'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
     'revision_delete_success' => 'Revize smazána',
     'revision_cannot_delete_latest' => 'Nelze smazat poslední revizi.'
-
 ];
\ No newline at end of file
index bd9c62bc1ff5fc40e85055e6e7d0df99079f0bd9..fa00491b02ddec6b07ba50d798ff13b51dc0cd81 100644 (file)
@@ -27,13 +27,14 @@ return [
     'social_account_register_instructions' => 'Pokud ještě nemáte náš účet, můžete se zaregistrovat pomocí vašeho účtu na :socialAccount.',
     'social_driver_not_found' => 'Doplněk pro tohoto správce identity nebyl nalezen.',
     'social_driver_not_configured' => 'Nastavení vašeho účtu na :socialAccount není správné. :socialAccount musí mít vaše svolení pro naší aplikaci vás přihlásit.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => 'Nelze zapisovat na cestu k souboru :filePath. Zajistěte aby se dalo nahrávat na server.',
     'cannot_get_image_from_url' => 'Nelze získat obrázek z adresy :url',
     'cannot_create_thumbs' => 'Server nedokáže udělat náhledy. Zkontrolujte, že rozšíření GD pro PHP je nainstalováno.',
     'server_upload_limit' => 'Server nepovoluje nahrávat tak veliké soubory. Zkuste prosím menší soubor.',
-    'uploaded'  => 'Server nepovoluje nahrávat tak veliké soubory. Zkuste prosím menší soubor.', //TODO to je nějaký podezřelý
+    'uploaded'  => 'Server nepovoluje nahrávat tak veliké soubory. Zkuste prosím menší soubor.',
     'image_upload_error' => 'Nastala chyba během nahrávání souboru',
     'image_upload_type_error' => 'Typ nahrávaného obrázku je neplatný.',
     'file_upload_timeout' => 'Nahrávání souboru trvalo příliš dlouho a tak bylo ukončeno.',
@@ -71,7 +72,7 @@ return [
     'cannot_add_comment_to_draft' => 'Nemůžete přidávat komentáře ke konceptu.',
     'comment_add' => 'Při přidávání / aktualizaci komentáře nastala chyba.',
     'comment_delete' => 'Při mazání komentáře nastala chyba.',
-    'empty_comment' => 'Nemůžete přidat prázdný komentář.', //This has a deep thinking value
+    'empty_comment' => 'Nemůžete přidat prázdný komentář.',
 
     // Error pages
     '404_page_not_found' => 'Stránka nenalezena',
index de98215223b7b1d8e20e2f0a6a50736127331b69..6281ff05845c0668cbbd09b92900b2220c14f59e 100644 (file)
@@ -6,7 +6,7 @@
  */
 return [
 
-    'previous' => '&laquo; Pøedchozí',
-    'next'     => 'Dal\9aí &raquo;',
+    'previous' => '\'&laquo; P',
+    'next'     => 'Dal',
 
 ];
index 1881dffae2ee1c7ea394cc9df1cf3f5660b3b758..bb6e6be09ac8e9a90f8d0cf3cf129949dbde1bce 100644 (file)
@@ -12,17 +12,24 @@ return [
     'settings_save_success' => 'Nastavení bylo uloženo',
 
     // App Settings
-    'app_settings' => 'Nastavení aplikace',
+    'app_customization' => 'Customization',
+    'app_features_security' => 'Features & Security',
     'app_name' => 'Název aplikace',
     'app_name_desc' => 'Název se bude zobrazovat v záhlaví této aplikace a v odesílaných emailech.',
     'app_name_header' => 'Zobrazovát název aplikace v záhlaví?',
+    'app_public_access' => 'Public Access',
+    'app_public_access_desc' => 'Enabling this option will allow visitors, that are not logged-in, to access content in your BookStack instance.',
+    'app_public_access_desc_guest' => 'Access for public visitors can be controlled through the "Guest" user.',
+    'app_public_access_toggle' => 'Allow public access',
     'app_public_viewing' => 'Povolit prohlížení veřejností?',
     'app_secure_images' => 'Nahrávat obrázky neveřejně a zabezpečeně?',
+    'app_secure_images_toggle' => 'Enable higher security image uploads',
     'app_secure_images_desc' => 'Z výkonnostních důvodů jsou všechny obrázky veřejné. Tato volba přidá do adresy obrázku náhodné číslo, aby nikdo neodhadnul adresu obrázku. Zajistěte ať adresáře nikomu nezobrazují seznam souborů.',
     'app_editor' => 'Editor stránek',
     'app_editor_desc' => 'Zvolte který editor budou užívat všichni uživatelé k úpravě stránek.',
     'app_custom_html' => 'Vlastní HTML kód pro sekci hlavičky (<head>).',
     'app_custom_html_desc' => 'Cokoliv sem napíšete bude přidáno na konec sekce <head> v každém místě této aplikace. To se hodí pro přidávání nebo změnu CSS stylů nebo přidání kódu pro analýzu používání (např.: google analytics.).',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'Logo aplikace',
     'app_logo_desc' => 'Obrázek by měl mít 43 pixelů na výšku. <br>Větší obrázky zmenšíme na tuto velikost.',
     'app_primary_color' => 'Hlavní barva aplikace',
@@ -31,13 +38,17 @@ return [
     'app_homepage_desc' => 'Zvolte pohled který se objeví jako úvodní stránka po přihlášení. Pokud zvolíte stránku, její specifická oprávnění budou ignorována (výjimka z výjimky 😜).',
     'app_homepage_select' => 'Zvolte stránku',
     'app_disable_comments' => 'Zakázání komentářů',
+    'app_disable_comments_toggle' => 'Disable comments',
     'app_disable_comments_desc' => 'Zakáže komentáře napříč všemi stránkami. Existující komentáře se přestanou zobrazovat.',
 
     // Registration Settings
     'reg_settings' => 'Nastavení registrace',
-    'reg_allow' => 'Povolit registrace?',
+    'reg_enable' => 'Enable Registration',
+    'reg_enable_toggle' => 'Enable registration',
+    'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
     'reg_default_role' => 'Role přiřazená po registraci',
-    'reg_confirm_email' => 'Vyžadovat ověření emailové adresy?',
+    'reg_email_confirmation' => 'Email Confirmation',
+    'reg_email_confirmation_toggle' => 'Require email confirmation',
     'reg_confirm_email_desc' => 'Pokud zapnete omezení emailové domény, tak bude ověřování emailové adresy vyžadováno vždy.',
     'reg_confirm_restrict_domain' => 'Omezit registraci podle domény',
     'reg_confirm_restrict_domain_desc' => 'Zadejte emailové domény, kterým bude povolena registrace uživatelů. Oddělujete čárkou. Uživatelům bude odeslán email s odkazem pro potvrzení vlastnictví emailové adresy. Bez potvrzení nebudou moci aplikaci používat. <br> Pozn.: Uživatelé si mohou emailovou adresu změnit po úspěšné registraci.',
@@ -50,7 +61,6 @@ return [
     'maint_image_cleanup_ignore_revisions' => 'Ignorovat obrázky v revizích',
     'maint_image_cleanup_run' => 'Spustit pročištění',
     'maint_image_cleanup_warning' => 'Nalezeno :count potenciálně nepoužitých obrázků. Jste si jistí, že je chcete smazat?',
-       
     'maint_image_cleanup_success' => 'Potenciálně nepoužité obrázky byly smazány. Celkem :count.',
     'maint_image_cleanup_nothing_found' => 'Žádné potenciálně nepoužité obrázky nebyly nalezeny. Nic nebylo smazáno.',
 
@@ -75,6 +85,7 @@ return [
     'role_manage_roles' => 'Správa rolí a jejich práv',
     'role_manage_entity_permissions' => 'Správa práv všech knih, kapitol a stránek',
     'role_manage_own_entity_permissions' => 'Správa práv vlastních knih, kapitol a stránek',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'Správa nastavení aplikace',
     'role_asset' => 'Práva děl',
     'role_asset_desc' => 'Tato práva řídí přístup k dílům v rámci systému. Specifická práva na knihách, kapitolách a stránkách překryjí tato nastavení.',
@@ -92,8 +103,17 @@ return [
     'user_profile' => 'Profil uživatele',
     'users_add_new' => 'Přidat nového uživatele',
     'users_search' => 'Vyhledávání uživatelů',
+    'users_details' => 'User Details',
+    'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.',
+    'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.',
     'users_role' => 'Uživatelské role',
+    'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
+    'users_password' => 'User Password',
+    'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 6 characters long.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => 'Přihlašovací identifikátory třetích stran',
+    'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your LDAP system.',
     'users_password_warning' => 'Vyplňujte pouze v případě, že chcete heslo změnit:',
     'users_system_public' => 'Symbolizuje libovolného veřejného návštěvníka, který navštívil vaší aplikaci. Nelze ho použít k přihlášení ale je přiřazen automaticky veřejnosti.',
     'users_delete' => 'Smazat uživatele',
@@ -107,11 +127,40 @@ return [
     'users_avatar' => 'Uživatelský obrázek',
     'users_avatar_desc' => 'Obrázek by měl být čtverec 256 pixelů široký. Bude oříznut do kruhu.',
     'users_preferred_language' => 'Upřednostňovaný jazyk',
+    'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
     'users_social_accounts' => 'Přidružené účty ze sociálních sítí',
-    'users_social_accounts_info' => 'Zde můžete přidat vaše účty ze sociálních sítí pro pohodlnější přihlašování. Zrušení přidružení zde neznamená, že tato aplikace pozbude práva číst detaily z vašeho účtu. Zakázat této aplikaci přístup k detailům vašeho účtu musíte přímo ve vašem profilu na dané sociální síti.',     
-    
+    'users_social_accounts_info' => 'Zde můžete přidat vaše účty ze sociálních sítí pro pohodlnější přihlašování. Zrušení přidružení zde neznamená, že tato aplikace pozbude práva číst detaily z vašeho účtu. Zakázat této aplikaci přístup k detailům vašeho účtu musíte přímo ve vašem profilu na dané sociální síti.',
     'users_social_connect' => 'Přidružit účet',
     'users_social_disconnect' => 'Zrušit přidružení',
     'users_social_connected' => 'Účet :socialAccount byl úspěšně přidružen k vašemu profilu.',
-    'users_social_disconnected' => 'Přidružení účtu :socialAccount k vašemu profilu bylo úspěšně zrušeno.'
+    'users_social_disconnected' => 'Přidružení účtu :socialAccount k vašemu profilu bylo úspěšně zrušeno.',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index 3e47fb0c39ca498565426e1f0ead5dbabf6c147a..13a8f790fc058856cbad7b2e1cb1c585815675d5 100644 (file)
@@ -1,27 +1,21 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages.
-    |
-    */
 
+    // Standard laravel validation lines
     'accepted'             => ':attribute musí být přijat.',
     'active_url'           => ':attribute není platnou URL adresou.',
     'after'                => ':attribute musí být datum po :date.',
-    'after_or_equal'       => ':attribute musí být datum :date nebo pozdější.',
     'alpha'                => ':attribute může obsahovat pouze písmena.',
     'alpha_dash'           => ':attribute může obsahovat pouze písmena, číslice, pomlčky a podtržítka. České znaky (á, é, í, ó, ú, ů, ž, š, č, ř, ď, ť, ň) nejsou podporovány.',
     'alpha_num'            => ':attribute může obsahovat pouze písmena a číslice.',
     'array'                => ':attribute musí být pole.',
     'before'               => ':attribute musí být datum před :date.',
-    'before_or_equal'      => 'Datum :attribute musí být před nebo rovno :date.',
     'between'              => [
         'numeric' => ':attribute musí být hodnota mezi :min a :max.',
         'file'    => ':attribute musí být větší než :min a menší než :max Kilobytů.',
@@ -31,16 +25,12 @@ return [
     'boolean'              => ':attribute musí být true nebo false',
     'confirmed'            => ':attribute nesouhlasí.',
     'date'                 => ':attribute musí být platné datum.',
-    'date_equals'          => 'The :attribute must be a date equal to :date.',
     'date_format'          => ':attribute není platný formát data podle :format.',
     'different'            => ':attribute a :other se musí lišit.',
     'digits'               => ':attribute musí být :digits pozic dlouhé.',
     'digits_between'       => ':attribute musí být dlouhé nejméně :min a nejvíce :max pozic.',
-    'dimensions'           => ':attribute má neplatné rozměry.',
-    'distinct'             => ':attribute má duplicitní hodnotu.',
     'email'                => ':attribute není platný formát.',
-    'exists'               => 'Zvolená hodnota pro :attribute není platná.',
-    'file'                 => ':attribute musí být soubor.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => ':attribute musí být vyplněno.',
     'gt'                   => [
         'numeric' => ':attribute musí být větší než :value.',
@@ -54,9 +44,10 @@ return [
         'string'  => 'Počet znaků :attribute musí být větší nebo rovno :value.',
         'array'   => 'Pole :attribute musí mít :value prvků nebo více.',
     ],
+    'exists'               => 'Zvolená hodnota pro :attribute není platná.',
     'image'                => ':attribute musí být obrázek.',
+    'image_extension'      => 'The :attribute must have a valid & supported image extension.',
     'in'                   => 'Zvolená hodnota pro :attribute je neplatná.',
-    'in_array'             => ':attribute není obsažen v :other.',
     'integer'              => ':attribute musí být celé číslo.',
     'ip'                   => ':attribute musí být platnou IP adresou.',
     'ipv4'                 => ':attribute musí být platná IPv4 adresa.',
@@ -81,21 +72,19 @@ return [
         'array'   => ':attribute nemůže obsahovat více než :max prvků.',
     ],
     'mimes'                => ':attribute musí být jeden z následujících datových typů :values.',
-    'mimetypes'            => ':attribute musí být jeden z následujících datových typů :values.',
     'min'                  => [
         'numeric' => ':attribute musí být větší než :min.',
         'file'    => ':attribute musí být větší než :min kB.',
         'string'  => ':attribute musí být delší než :min znaků.',
         'array'   => ':attribute musí obsahovat více než :min prvků.',
     ],
+    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => 'Zvolená hodnota pro :attribute je neplatná.',
     'not_regex'            => ':attribute musí být regulární výraz.',
     'numeric'              => ':attribute musí být číslo.',
-    'present'              => ':attribute musí být vyplněno.',
     'regex'                => ':attribute nemá správný formát.',
     'required'             => ':attribute musí být vyplněno.',
     'required_if'          => ':attribute musí být vyplněno pokud :other je :value.',
-    'required_unless'      => ':attribute musí být vyplněno dokud :other je v :values.',
     'required_with'        => ':attribute musí být vyplněno pokud :values je vyplněno.',
     'required_with_all'    => ':attribute musí být vyplněno pokud :values je zvoleno.',
     'required_without'     => ':attribute musí být vyplněno pokud :values není vyplněno.',
@@ -107,46 +96,19 @@ return [
         'string'  => ':attribute musí být přesně :size znaků dlouhý.',
         'array'   => ':attribute musí obsahovat právě :size prvků.',
     ],
-    'starts_with'          => 'The :attribute must start with one of the following: :values',
     'string'               => ':attribute musí být řetězec znaků.',
     'timezone'             => ':attribute musí být platná časová zóna.',
     'unique'               => ':attribute musí být unikátní.',
-    'uploaded'             => 'Nahrávání :attribute se nezdařilo.',
     'url'                  => 'Formát :attribute je neplatný.',
-    'uuid'                 => ':attribute musí být validní UUID.',
-
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
+    'uploaded'             => 'Nahrávání :attribute se nezdařilo.',
 
+    // Custom validation lines
     'custom' => [
-        'attribute-name' => [
-            'rule-name' => 'custom-message',
-        ],
         'password-confirm' => [
             'required_with' => 'Password confirmation required',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
-    'attributes' => [
-        'password' => 'heslo',
-    ],
+    // Custom validation attributes
+    'attributes' => [],
 ];
index 35b2c9f8a50a3fd5a04e4384e8977bfa97d7340e..170a1910825ac8637a702183c274b87f55dda810 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => 'erstellt Seite',
     'page_create_notification'    => 'Die Seite wurde erfolgreich erstellt.',
@@ -38,7 +36,7 @@ return [
     'book_sort_notification'      => 'Das Buch wurde erfolgreich umsortiert.',
 
     // Bookshelves
-    'bookshelf_create'                 => 'erstellt Bücherregal',
+    'bookshelf_create'            => 'erstellt Bücherregal',
     'bookshelf_create_notification'    => 'Das Bücherregal wurde erfolgreich erstellt',
     'bookshelf_update'                 => 'aktualisiert Bücherregal',
     'bookshelf_update_notification'    => 'Das Bücherregal wurde erfolgreich aktualisiert',
index 46d4070b8c1e47d06df0d3656c61b7ec465e8824..3d0db9dc88e886e84f5cdb1be71357cfe75af3b2 100644 (file)
@@ -1,31 +1,27 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => 'Die eingegebenen Anmeldedaten sind ungültig.',
     'throttle' => 'Zu viele Anmeldeversuche. Bitte versuchen Sie es in :seconds Sekunden erneut.',
-    /**
-     * Login & Register
-     */
+
+    // Login & Register
     'sign_up' => 'Registrieren',
     'log_in' => 'Anmelden',
     'log_in_with' => 'Anmelden mit :socialDriver',
     'sign_up_with' => 'Registrieren mit :socialDriver',
     'logout' => 'Abmelden',
+
     'name' => 'Name',
     'username' => 'Benutzername',
     'email' => 'E-Mail',
     'password' => 'Passwort',
     'password_confirm' => 'Passwort best&auml;tigen',
-    'password_hint' => 'Mindestlänge: 5 Zeichen',
+    'password_hint' => 'Mindestlänge: 7 Zeichen',
     'forgot_password' => 'Passwort vergessen?',
     'remember_me' => 'Angemeldet bleiben',
     'ldap_email_hint' => 'Bitte geben Sie eine E-Mail-Adresse ein, um diese mit dem Account zu nutzen.',
@@ -35,14 +31,15 @@ return [
     'social_login' => 'Mit Sozialem Netzwerk anmelden',
     'social_registration' => 'Mit Sozialem Netzwerk registrieren',
     'social_registration_text' => 'Mit einer dieser Dienste registrieren oder anmelden',
+
     'register_thanks' => 'Vielen Dank für Ihre Registrierung!',
     'register_confirm' => 'Bitte prüfen Sie Ihren Posteingang und bestätigen Sie die Registrierung.',
     'registrations_disabled' => 'Eine Registrierung ist momentan nicht möglich',
     'registration_email_domain_invalid' => 'Sie können sich mit dieser E-Mail nicht registrieren.',
     'register_success' => 'Vielen Dank für Ihre Registrierung! Die Daten sind gespeichert und Sie sind angemeldet.',
-    /**
-     * Password Reset
-     */
+
+
+    // Password Reset
     'reset_password' => 'Passwort vergessen',
     'reset_password_send_instructions' => 'Bitte geben Sie Ihre E-Mail-Adresse ein. Danach erhalten Sie eine E-Mail mit einem Link zum Zurücksetzen Ihres Passwortes.',
     'reset_password_send_button' => 'Passwort zurücksetzen',
@@ -51,9 +48,9 @@ return [
     'email_reset_subject' => 'Passwort zurücksetzen für :appName',
     'email_reset_text' => 'Sie erhalten diese E-Mail, weil jemand versucht hat, Ihr Passwort zurückzusetzen.',
     'email_reset_not_requested' => 'Wenn Sie das nicht waren, brauchen Sie nichts weiter zu tun.',
-    /**
-     * Email Confirmation
-     */
+
+
+    // Email Confirmation
     'email_confirm_subject' => 'Bestätigen Sie Ihre E-Mail-Adresse für :appName',
     'email_confirm_greeting' => 'Danke, dass Sie sich für :appName registriert haben!',
     'email_confirm_text' => 'Bitte bestätigen Sie Ihre E-Mail-Adresse, indem Sie auf die Schaltfläche klicken:',
@@ -61,9 +58,20 @@ return [
     'email_confirm_send_error' => 'Leider konnte die für die Registrierung notwendige E-Mail zur bestätigung Ihrer E-Mail-Adresse nicht versandt werden. Bitte kontaktieren Sie den Systemadministrator!',
     'email_confirm_success' => 'Ihre E-Mail-Adresse wurde best&auml;tigt!',
     'email_confirm_resent' => 'Bestätigungs-E-Mail wurde erneut versendet, bitte überprüfen Sie Ihren Posteingang.',
+
     'email_not_confirmed' => 'E-Mail-Adresse ist nicht bestätigt',
     'email_not_confirmed_text' => 'Ihre E-Mail-Adresse ist bisher nicht bestätigt.',
     'email_not_confirmed_click_link' => 'Bitte klicken Sie auf den Link in der E-Mail, die Sie nach der Registrierung erhalten haben.',
     'email_not_confirmed_resend' => 'Wenn Sie die E-Mail nicht erhalten haben, können Sie die Nachricht erneut anfordern. Füllen Sie hierzu bitte das folgende Formular aus:',
     'email_not_confirmed_resend_button' => 'Bestätigungs-E-Mail erneut senden',
-];
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
+];
\ No newline at end of file
index 97b48ce4d623849c75b75814af36a20b358596e8..94e59d154f4f4b59a3a110df0ca7904414b6ae0a 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => 'Abbrechen',
     'confirm' => 'Bestätigen',
     'back' => 'Zurück',
@@ -13,18 +14,14 @@ return [
     'toggle_all' => 'Alle umschalten',
     'more' => 'Mehr',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => 'Name',
     'description' => 'Beschreibung',
     'role' => 'Rolle',
     'cover_image' => 'Titelbild',
     'cover_image_description' => 'Das Bild sollte eine Auflösung von 440x250px haben.',
-
-    /**
-     * Actions
-     */
+    
+    // Actions
     'actions' => 'Aktionen',
     'view' => 'Anzeigen',
     'view_all' => 'Alle anzeigen',
@@ -43,13 +40,15 @@ return [
     'add' => 'Hinzufügen',
 
     // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
     'sort_name' => 'Name',
     'sort_created_at' => 'Erstellungsdatum',
     'sort_updated_at' => 'Aktualisierungsdatum',
 
-    /**
-     * Misc
-     */
+    // Misc
     'deleted_user' => 'Gelöschte Benutzer',
     'no_activity' => 'Keine Aktivitäten zum Anzeigen',
     'no_items' => 'Keine Einträge gefunden.',
@@ -60,10 +59,10 @@ return [
     'grid_view' => 'Gitteransicht',
     'list_view' => 'Listenansicht',
     'default' => 'Voreinstellung',
+    'breadcrumb' => 'Breadcrumb',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => 'Profil ansehen',
     'edit_profile' => 'Profil bearbeiten',
 
@@ -71,9 +70,7 @@ return [
     'tab_info' => 'Info',
     'tab_content' => 'Inhalt',
 
-    /**
-     * Email Content
-     */
+    // Email Content
     'email_action_help' => 'Sollte es beim Anklicken der Schaltfläche ":action_text" Probleme geben, öffnen Sie folgende URL in Ihrem Browser:',
     'email_rights' => 'Alle Rechte vorbehalten',
 ];
index af07f26982077eb7e58c06cd772a58cc2aca19a6..4e56722a8cda3dac9dac2d0cb74f27281907d96b 100644 (file)
@@ -1,8 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
-    /**
-     * Image Manager
-     */
+
+    // Image Manager
     'image_select' => 'Bild auswählen',
     'image_all' => 'Alle',
     'image_all_title' => 'Alle Bilder anzeigen',
@@ -22,9 +24,8 @@ return [
     'image_update_success' => 'Bilddetails erfolgreich aktualisiert',
     'image_delete_success' => 'Bild erfolgreich gelöscht',
     'image_upload_remove' => 'Entfernen',
-    /**
-     * Code editor
-     */
+
+    // Code Editor
     'code_editor' => 'Code editieren',
     'code_language' => 'Code Sprache',
     'code_content' => 'Code Inhalt',
index d674195434a3f1143facc2e909db74bd73d112de..164d7a79450773428630e3a90e158dd9b7c5d378 100644 (file)
@@ -1,8 +1,11 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
-    /**
-     * Shared
-     */
+
+    // Shared
     'recently_created' => 'Kürzlich angelegt',
     'recently_created_pages' => 'Kürzlich angelegte Seiten',
     'recently_updated_pages' => 'Kürzlich aktualisierte Seiten',
@@ -30,16 +33,14 @@ return [
     'export_html' => 'HTML-Datei',
     'export_pdf' => 'PDF-Datei',
     'export_text' => 'Textdatei',
-    /**
-     * Permissions and restrictions
-     */
+
+    // Permissions and restrictions
     'permissions' => 'Berechtigungen',
     'permissions_intro' => 'Wenn individuelle Berechtigungen aktiviert werden, überschreiben diese Einstellungen durch Rollen zugewiesene Berechtigungen.',
     'permissions_enable' => 'Individuelle Berechtigungen aktivieren',
     'permissions_save' => 'Berechtigungen speichern',
-    /**
-     * Search
-     */
+
+    // Search
     'search_results' => 'Suchergebnisse',
     'search_total_results_found' => ':count Ergebnis gefunden|:count Ergebnisse gesamt',
     'search_clear' => 'Filter löschen',
@@ -64,9 +65,7 @@ return [
     'search_set_date' => 'Datum auswählen',
     'search_update' => 'Suche aktualisieren',
 
-    /*
-     * Shelves
-     */
+    // Shelves
     'shelf' => 'Regal',
     'shelves' => 'Regale',
     'x_shelves' => ':count Regal|:count Regale',
@@ -98,9 +97,7 @@ return [
     'shelves_copy_permissions_explain' => 'Hiermit werden die Berechtigungen des aktuellen Regals auf alle enthaltenen Bücher übertragen. Überprüfen Sie vor der Aktivierung, ob alle Berechtigungsänderungen am aktuellen Regal gespeichert wurden.',
     'shelves_copy_permission_success' => 'Regal-Berechtigungen wurden zu :count Büchern kopiert',
 
-    /**
-     * Books
-     */
+    // Books
     'book' => 'Buch',
     'books' => 'Bücher',
     'x_books' => ':count Buch|:count Bücher',
@@ -138,9 +135,8 @@ return [
     'books_sort_chapters_last' => 'Kapitel zuletzt',
     'books_sort_show_other' => 'Andere Bücher anzeigen',
     'books_sort_save' => 'Neue Reihenfolge speichern',
-    /**
-     * Chapters
-     */
+
+    // Chapters
     'chapter' => 'Kapitel',
     'chapters' => 'Kapitel',
     'x_chapters' => ':count Kapitel',
@@ -157,17 +153,13 @@ return [
     'chapters_move' => 'Kapitel verschieben',
     'chapters_move_named' => 'Kapitel ":chapterName" verschieben',
     'chapter_move_success' => 'Das Kapitel wurde in das Buch ":bookName" verschoben.',
-    'pages_copy' => 'Seite kopieren',
-    'pages_copy_desination' => 'Ziel',
-    'pages_copy_success' => 'Seite erfolgreich kopiert',
     'chapters_permissions' => 'Kapitel-Berechtigungen',
     'chapters_empty' => 'Aktuell sind keine Kapitel diesem Buch hinzugefügt worden.',
     'chapters_permissions_active' => 'Kapitel-Berechtigungen aktiv',
     'chapters_permissions_success' => 'Kapitel-Berechtigungenen aktualisisert',
     'chapters_search_this' => 'Dieses Kapitel durchsuchen',
-    /**
-     * Pages
-     */
+
+    // Pages
     'page' => 'Seite',
     'pages' => 'Seiten',
     'x_pages' => ':count Seite|:count Seiten',
@@ -184,7 +176,7 @@ return [
     'pages_delete_confirm' => 'Sind Sie sicher, dass Sie diese Seite löschen möchen?',
     'pages_delete_draft_confirm' => 'Sind Sie sicher, dass Sie diesen Seitenentwurf löschen möchten?',
     'pages_editing_named' => 'Seite ":pageName" bearbeiten',
-    'pages_edit_toggle_header' => 'Hauptmenü anzeigen/verstecken',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'Entwurf speichern',
     'pages_edit_draft' => 'Seitenentwurf bearbeiten',
     'pages_editing_draft' => 'Seitenentwurf bearbeiten',
@@ -206,6 +198,9 @@ return [
     'pages_not_in_chapter' => 'Seite ist in keinem Kapitel',
     'pages_move' => 'Seite verschieben',
     'pages_move_success' => 'Seite nach ":parentName" verschoben',
+    'pages_copy' => 'Seite kopieren',
+    'pages_copy_desination' => 'Ziel',
+    'pages_copy_success' => 'Seite erfolgreich kopiert',
     'pages_permissions' => 'Seiten Berechtigungen',
     'pages_permissions_success' => 'Seiten Berechtigungen aktualisiert',
     'pages_revision' => 'Version',
@@ -215,6 +210,8 @@ return [
     'pages_revisions_created_by' => 'Erstellt von',
     'pages_revisions_date' => 'Versionsdatum',
     'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
     'pages_revisions_changelog' => 'Änderungsprotokoll',
     'pages_revisions_changes' => 'Änderungen',
     'pages_revisions_current' => 'Aktuelle Version',
@@ -236,18 +233,21 @@ return [
         'message' => ':start :time. Achten Sie darauf, keine Änderungen von anderen Benutzern zu überschreiben!',
     ],
     'pages_draft_discarded' => 'Entwurf verworfen. Der aktuelle Seiteninhalt wurde geladen.',
-    /**
-     * Editor sidebar
-     */
+    'pages_specific' => 'Specific Page',
+    'pages_is_template' => 'Page Template',
+
+    // Editor Sidebar
     'page_tags' => 'Seiten-Schlagwörter',
     'chapter_tags' => 'Kapitel-Schlagwörter',
     'book_tags' => 'Buch-Schlagwörter',
     'shelf_tags' => 'Regal-Schlagwörter',
     'tag' => 'Schlagwort',
     'tags' =>  'Schlagwörter',
+    'tag_name' =>  'Tag Name',
     'tag_value' => 'Inhalt (Optional)',
     'tags_explain' => "Fügen Sie Schlagwörter hinzu, um Ihren Inhalt zu kategorisieren.\nSie können einen erklärenden Inhalt hinzufügen, um eine genauere Unterteilung vorzunehmen.",
     'tags_add' => 'Weiteres Schlagwort hinzufügen',
+    'tags_remove' => 'Remove this tag',
     'attachments' => 'Anhänge',
     'attachments_explain' => 'Sie können auf Ihrer Seite Dateien hochladen oder Links hinzufügen. Diese werden in der Seitenleiste angezeigt.',
     'attachments_explain_instant_save' => 'Änderungen werden direkt gespeichert.',
@@ -273,18 +273,22 @@ return [
     'attachments_file_uploaded' => 'Datei erfolgreich hochgeladen',
     'attachments_file_updated' => 'Datei erfolgreich aktualisiert',
     'attachments_link_attached' => 'Link erfolgreich der Seite hinzugefügt',
-    /**
-     * Profile View
-     */
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
+
+    // Profile View
     'profile_user_for_x' => 'Benutzer seit :time',
     'profile_created_content' => 'Erstellte Inhalte',
     'profile_not_created_pages' => ':userName hat noch keine Seiten erstellt.',
     'profile_not_created_chapters' => ':userName hat noch keine Kapitel erstellt.',
     'profile_not_created_books' => ':userName hat noch keine Bücher erstellt.',
     'profile_not_created_shelves' => ':userName hat noch keine Regale erstellt.',
-    /**
-     * Comments
-     */
+
+    // Comments
     'comment' => 'Kommentar',
     'comments' => 'Kommentare',
     'comment_add' => 'Kommentieren',
@@ -302,11 +306,9 @@ return [
     'comment_delete_confirm' => 'Möchten Sie diesen Kommentar wirklich löschen?',
     'comment_in_reply_to' => 'Antwort auf :commentId',
 
-    /**
-     * Revision
-     */
+    // Revision
     'revision_delete_confirm' => 'Sind Sie sicher, dass Sie diese Revision löschen wollen?',
     'revision_restore_confirm' => 'Sind Sie sicher, dass Sie diese Revision wiederherstellen wollen? Der aktuelle Seiteninhalt wird ersetzt.',
     'revision_delete_success' => 'Revision gelöscht',
     'revision_cannot_delete_latest' => 'Die letzte Version kann nicht gelöscht werden.'
-];
+];
\ No newline at end of file
index 2cd9d8b30302369ce66fd037b875726e0d2c0580..ff2bf8653d85f59cc3e9e976062fd4295e7127e9 100644 (file)
@@ -1,11 +1,13 @@
 <?php
+/**
+ * Text shown in error messaging.
+ */
 return [
-    /**
-     * Error text strings.
-     */
-    // Pages
+
+    // Permissions
     'permission' => 'Sie haben keine Berechtigung, auf diese Seite zuzugreifen.',
     'permissionJson' => 'Sie haben keine Berechtigung, die angeforderte Aktion auszuführen.',
+
     // Auth
     'error_user_exists_different_creds' => 'Ein Benutzer mit der E-Mail-Adresse :email ist bereits mit anderen Anmeldedaten registriert.',
     'email_already_confirmed' => 'Die E-Mail-Adresse ist bereits bestätigt. Bitte melden Sie sich an.',
@@ -27,6 +29,8 @@ return [
     'social_account_register_instructions' => 'Wenn Sie bisher keinen Social-Media Konto besitzen, können Sie ein solches Konto mit der :socialAccount Option anlegen.',
     'social_driver_not_found' => 'Treiber für Social-Media-Konten nicht gefunden',
     'social_driver_not_configured' => 'Ihr :socialAccount-Konto ist nicht korrekt konfiguriert.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
+
     // System
     'path_not_writable' => 'Die Datei kann nicht in den angegebenen Pfad :filePath hochgeladen werden. Stellen Sie sicher, dass dieser Ordner auf dem Server beschreibbar ist.',
     'cannot_get_image_from_url' => 'Bild konnte nicht von der URL :url geladen werden.',
@@ -40,21 +44,25 @@ return [
     // Attachments
     'attachment_page_mismatch' => 'Die Seite stimmte nach dem Hochladen des Anhangs nicht überein.',
     'attachment_not_found' => 'Anhang konnte nicht gefunden werden.',
+
     // Pages
     'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stellen Sie sicher, dass Sie mit dem Internet verbunden sind, bevor Sie den Entwurf dieser Seite speichern.',
     'page_custom_home_deletion' => 'Eine als Startseite gesetzte Seite kann nicht gelöscht werden.',
+
     // Entities
     'entity_not_found' => 'Eintrag nicht gefunden',
-    'book_not_found' => 'Buch nicht gefunden',
     'bookshelf_not_found' => 'Regal nicht gefunden',
+    'book_not_found' => 'Buch nicht gefunden',
     'page_not_found' => 'Seite nicht gefunden',
     'chapter_not_found' => 'Kapitel nicht gefunden',
     'selected_book_not_found' => 'Das gewählte Buch wurde nicht gefunden.',
     'selected_book_chapter_not_found' => 'Das gewählte Buch oder Kapitel wurde nicht gefunden.',
     'guests_cannot_save_drafts' => 'Gäste können keine Entwürfe speichern',
+
     // Users
     'users_cannot_delete_only_admin' => 'Sie können den einzigen Administrator nicht löschen.',
     'users_cannot_delete_guest' => 'Sie können den Gast-Benutzer nicht löschen',
+
     // Roles
     'role_cannot_be_edited' => 'Diese Rolle kann nicht bearbeitet werden.',
     'role_system_cannot_be_deleted' => 'Dies ist eine Systemrolle und kann nicht gelöscht werden',
@@ -67,11 +75,13 @@ return [
     'comment_add' => 'Beim Hinzufügen des Kommentars ist ein Fehler aufgetreten.',
     'comment_delete' => 'Beim Löschen des Kommentars ist ein Fehler aufgetreten.',
     'empty_comment' => 'Kann keinen leeren Kommentar hinzufügen',
+
     // Error pages
     '404_page_not_found' => 'Seite nicht gefunden',
     'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Sie angefordert haben, wurde nicht gefunden.',
     'return_home' => 'Zurück zur Startseite',
     'error_occurred' => 'Es ist ein Fehler aufgetreten',
     'app_down' => ':appName befindet sich aktuell im Wartungsmodus.',
-    'back_soon' => 'Wir werden so schnell wie möglich wieder online sein.'
+    'back_soon' => 'Wir werden so schnell wie möglich wieder online sein.',
+
 ];
index 6ed0e30f0a27a8eee592f5ea1d976e2223da8343..5777671378d589b75becec12ade5e4dec86ce286 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; Vorherige',
     'next'     => 'Nächste &raquo;',
 
index 440c5f92d1ade58cf3ecc83bfa97cb8b85bd5c8c..74149a7eda09f40a2f07d84ff535584a6882f8de 100644 (file)
@@ -1,20 +1,13 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => 'Passwörter müssen aus mindestens sechs Zeichen bestehen und mit der eingegebenen Wiederholung übereinstimmen.',
-    'user' => 'Es wurde kein Benutzer mit dieser E-Mail-Adresse gefunden.',
+    'user' => "Es wurde kein Benutzer mit dieser E-Mail-Adresse gefunden.",
     'token' => 'Dieser Link zum Zurücksetzen des Passwortes ist ungültig!',
     'sent' => 'Der Link zum Zurücksetzen Ihres Passwortes wurde Ihnen per E-Mail zugesendet.',
     'reset' => 'Ihr Passwort wurde zurückgesetzt!',
index 11050924ec5159b4d1b33826ca0ef17e32cf1f90..fa7df43f8893c8eaeab6487d112e09612a190cda 100644 (file)
@@ -1,16 +1,17 @@
 <?php
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
+
+    // Common Messages
     'settings' => 'Einstellungen',
     'settings_save' => 'Einstellungen speichern',
     'settings_save_success' => 'Einstellungen gespeichert',
-    /**
-     * App settings
-     */
+
+    // App Settings
     'app_customization' => 'Personalisierung',
     'app_features_security' => 'Funktionen & Sicherheit',
     'app_name' => 'Anwendungsname',
@@ -20,27 +21,29 @@ return [
     'app_public_access_desc' => 'Wenn Sie diese Option aktivieren, können Besucher, die nicht angemeldet sind, auf Inhalte in Ihrer BookStack-Instanz zugreifen.',
     'app_public_access_desc_guest' => 'Der Zugang für öffentliche Besucher kann über den Benutzer "Guest" gesteuert werden.',
     'app_public_access_toggle' => 'Öffentlichen Zugriff erlauben',
-
     'app_public_viewing' => 'Öffentliche Ansicht erlauben?',
     'app_secure_images' => 'Erhöhte Sicherheit für hochgeladene Bilder aktivieren?',
+    'app_secure_images_toggle' => 'Enable higher security image uploads',
     'app_secure_images_desc' => 'Aus Leistungsgründen sind alle Bilder öffentlich sichtbar. Diese Option fügt zufällige, schwer zu eratene, Zeichenketten zu Bild-URLs hinzu. Stellen sie sicher, dass Verzeichnisindizes deaktiviert sind, um einen einfachen Zugriff zu verhindern.',
     'app_editor' => 'Seiteneditor',
     'app_editor_desc' => 'Wählen Sie den Editor aus, der von allen Benutzern genutzt werden soll, um Seiten zu editieren.',
     'app_custom_html' => 'Benutzerdefinierter HTML <head> Inhalt',
     'app_custom_html_desc' => 'Jeder Inhalt, der hier hinzugefügt wird, wird am Ende der <head> Sektion jeder Seite eingefügt. Diese kann praktisch sein, um CSS Styles anzupassen oder Analytics-Code hinzuzufügen.',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'Anwendungslogo',
-    'app_logo_desc' => "Dieses Bild sollte 43px hoch sein.\nGrößere Bilder werden verkleinert.",
+    'app_logo_desc' => 'Dieses Bild sollte 43px hoch sein.
+Größere Bilder werden verkleinert.',
     'app_primary_color' => 'Primäre Anwendungsfarbe',
-    'app_primary_color_desc' => "Dies sollte ein HEX Wert sein.\nWenn Sie nicht eingeben, wird die Anwendung auf die Standardfarbe zurückgesetzt.",
+    'app_primary_color_desc' => 'Dies sollte ein HEX Wert sein.
+Wenn Sie nicht eingeben, wird die Anwendung auf die Standardfarbe zurückgesetzt.',
     'app_homepage' => 'Startseite der Anwendung',
     'app_homepage_desc' => 'Wählen Sie eine Seite als Startseite aus, die statt der Standardansicht angezeigt werden soll. Seitenberechtigungen werden für die ausgewählten Seiten ignoriert.',
     'app_homepage_select' => 'Wählen Sie eine Seite aus',
     'app_disable_comments' => 'Kommentare deaktivieren',
     'app_disable_comments_toggle' => 'Kommentare deaktivieren',
     'app_disable_comments_desc' => 'Deaktiviert Kommentare über alle Seiten in der Anwendung. Vorhandene Kommentare werden nicht angezeigt.',
-    /**
-     * Registration settings
-     */
+
+    // Registration Settings
     'reg_settings' => 'Registrierungseinstellungen',
     'reg_enable' => 'Registrierung erlauben?',
     'reg_enable_toggle' => 'Registrierung erlauben',
@@ -50,25 +53,21 @@ return [
     'reg_email_confirmation_toggle' => 'Bestätigung per E-Mail erforderlich',
     'reg_confirm_email_desc' => 'Falls die Einschränkung für Domains genutzt wird, ist die Bestätigung per E-Mail zwingend erforderlich und der untenstehende Wert wird ignoriert.',
     'reg_confirm_restrict_domain' => 'Registrierung auf bestimmte Domains einschränken',
-    'reg_confirm_restrict_domain_desc' => "Fügen sie eine durch Komma getrennte Liste von Domains hinzu, auf die die Registrierung eingeschränkt werden soll. Benutzern wird eine E-Mail gesendet, um ihre E-Mail Adresse zu bestätigen, bevor sie diese Anwendung nutzen können.\nHinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung ändern.",
+    'reg_confirm_restrict_domain_desc' => 'Fügen sie eine durch Komma getrennte Liste von Domains hinzu, auf die die Registrierung eingeschränkt werden soll. Benutzern wird eine E-Mail gesendet, um ihre E-Mail Adresse zu bestätigen, bevor sie diese Anwendung nutzen können.
+Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung ändern.',
     'reg_confirm_restrict_domain_placeholder' => 'Keine Einschränkung gesetzt',
 
-    /**
-     * Maintenance settings
-     */
-
+    // Maintenance settings
     'maint' => 'Wartung',
     'maint_image_cleanup' => 'Bilder bereinigen',
-    'maint_image_cleanup_desc' => 'Überprüft Seiten- und Versionsinhalte auf ungenutzte und mehrfach vorhandene Bilder. Erstellen Sie vor dem Start ein Backup Ihrer Datenbank und Bilder.',
+    'maint_image_cleanup_desc' => "Überprüft Seiten- und Versionsinhalte auf ungenutzte und mehrfach vorhandene Bilder. Erstellen Sie vor dem Start ein Backup Ihrer Datenbank und Bilder.",
     'maint_image_cleanup_ignore_revisions' => 'Bilder in Versionen ignorieren',
     'maint_image_cleanup_run' => 'Reinigung starten',
     'maint_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchten Sie diese Bilder löschen?',
     'maint_image_cleanup_success' => ':count eventuell unbenutze Bilder wurden gefunden und gelöscht.',
     'maint_image_cleanup_nothing_found' => 'Keine unbenutzen Bilder gefunden. Nichts zu löschen!',
 
-    /**
-     * Role settings
-     */
+    // Role Settings
     'roles' => 'Rollen',
     'role_user_roles' => 'Benutzer-Rollen',
     'role_create' => 'Neue Rolle anlegen',
@@ -89,6 +88,7 @@ return [
     'role_manage_roles' => 'Rollen und Rollen-Berechtigungen verwalten',
     'role_manage_entity_permissions' => 'Alle Buch-, Kapitel- und Seiten-Berechtigungen verwalten',
     'role_manage_own_entity_permissions' => 'Nur Berechtigungen eigener Bücher, Kapitel und Seiten verwalten',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'Globaleinstellungen verwalten',
     'role_asset' => 'Berechtigungen',
     'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungenen.',
@@ -100,9 +100,8 @@ return [
     'role_update_success' => 'Rolle erfolgreich gespeichert',
     'role_users' => 'Dieser Rolle zugeordnete Benutzer',
     'role_users_none' => 'Bisher sind dieser Rolle keine Benutzer zugeordnet',
-    /**
-     * Users
-     */
+
+    // Users
     'users' => 'Benutzer',
     'user_profile' => 'Benutzerprofil',
     'users_add_new' => 'Benutzer hinzufügen',
@@ -114,6 +113,8 @@ return [
     'users_role_desc' => 'Wählen Sie aus, welchen Rollen dieser Benutzer zugeordnet werden soll. Wenn ein Benutzer mehreren Rollen zugeordnet ist, werden die Berechtigungen dieser Rollen gestapelt und er erhält alle Fähigkeiten der zugewiesenen Rollen.',
     'users_password' => 'Benutzerpasswort',
     'users_password_desc' => 'Legen Sie ein Passwort fest, mit dem Sie sich anmelden möchten. Diese muss mindestens 5 Zeichen lang sein.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => 'Externe Authentifizierungs-ID',
     'users_external_auth_id_desc' => 'Dies ist die ID, die verwendet wird, um diesen Benutzer bei der Kommunikation mit Ihrem LDAP-System abzugleichen.',
     'users_password_warning' => 'Füllen Sie die folgenden Felder nur aus, wenn Sie Ihr Passwort ändern möchten:',
@@ -136,4 +137,33 @@ return [
     'users_social_disconnect' => 'Social-Media-Konto lösen',
     'users_social_connected' => ':socialAccount-Konto wurde erfolgreich mit dem Profil verknüpft.',
     'users_social_disconnected' => ':socialAccount-Konto wurde erfolgreich vom Profil gelöst.',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index 84faeebb770cca2ec98c1fba560f184bc17d726f..1cf2176d5283129f916a755b3df083f52a1dff3e 100644 (file)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | following language lines contain default error messages used by
-    | validator class. Some of these rules have multiple versions such
-    | as size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => ':attribute muss akzeptiert werden.',
     'active_url'           => ':attribute ist keine valide URL.',
     'after'                => ':attribute muss ein Datum nach :date sein.',
@@ -35,13 +30,41 @@ return [
     'digits'               => ':attribute muss :digits Stellen haben.',
     'digits_between'       => ':attribute muss zwischen :min und :max Stellen haben.',
     'email'                => ':attribute muss eine valide E-Mail-Adresse sein.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => ':attribute ist erforderlich.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => ':attribute ist ungültig.',
     'image'                => ':attribute muss ein Bild sein.',
     'image_extension'      => ':attribute muss eine gültige und unterstützte Bild-Dateiendung haben.',
     'in'                   => ':attribute ist ungültig.',
     'integer'              => ':attribute muss eine Zahl sein.',
     'ip'                   => ':attribute muss eine valide IP-Adresse sein.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => ':attribute darf nicht größer als :max sein.',
         'file'    => ':attribute darf nicht größer als :max Kilobyte sein.',
@@ -57,6 +80,7 @@ return [
     ],
     'no_double_extension'  => ':attribute darf nur eine gültige Dateiendung',
     'not_in'               => ':attribute ist ungültig.',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attribute muss eine Zahl sein.',
     'regex'                => ':attribute ist in einem ungültigen Format.',
     'required'             => ':attribute ist erforderlich.',
@@ -78,37 +102,13 @@ return [
     'url'                  => ':attribute ist kein valides Format.',
     'uploaded'             => 'Die Datei konnte nicht hochgeladen werden. Der Server akzeptiert möglicherweise keine Dateien dieser Größe.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
-        'attribute-name' => [
-            'rule-name' => 'custom-message',
-        ],
         'password-confirm' => [
             'required_with' => 'Passwortbestätigung erforderlich',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
index c82c9e0c449673b88af05595e27da895900530c3..170a1910825ac8637a702183c274b87f55dda810 100644 (file)
@@ -1,6 +1,48 @@
 <?php
-
-// Extends 'de'
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
-    //
-];
\ No newline at end of file
+
+    // Pages
+    'page_create'                 => 'erstellt Seite',
+    'page_create_notification'    => 'Die Seite wurde erfolgreich erstellt.',
+    'page_update'                 => 'aktualisiert Seite',
+    'page_update_notification'    => 'Die Seite wurde erfolgreich aktualisiert.',
+    'page_delete'                 => 'löscht Seite',
+    'page_delete_notification'    => 'Die Seite wurde erfolgreich gelöscht.',
+    'page_restore'                => 'stellt Seite wieder her',
+    'page_restore_notification'   => 'Die Seite wurde erfolgreich wiederhergestellt.',
+    'page_move'                   => 'verschiebt Seite',
+
+    // Chapters
+    'chapter_create'              => 'erstellt Kapitel',
+    'chapter_create_notification' => 'Das Kapitel wurde erfolgreich erstellt.',
+    'chapter_update'              => 'aktualisiert Kapitel',
+    'chapter_update_notification' => 'Das Kapitel wurde erfolgreich aktualisiert.',
+    'chapter_delete'              => 'löscht Kapitel',
+    'chapter_delete_notification' => 'Das Kapitel wurde erfolgreich gelöscht.',
+    'chapter_move'                => 'verschiebt Kapitel',
+
+    // Books
+    'book_create'                 => 'erstellt Buch',
+    'book_create_notification'    => 'Das Buch wurde erfolgreich erstellt.',
+    'book_update'                 => 'aktualisiert Buch',
+    'book_update_notification'    => 'Das Buch wurde erfolgreich aktualisiert.',
+    'book_delete'                 => 'löscht Buch',
+    'book_delete_notification'    => 'Das Buch wurde erfolgreich gelöscht.',
+    'book_sort'                   => 'sortiert Buch',
+    'book_sort_notification'      => 'Das Buch wurde erfolgreich umsortiert.',
+
+    // Bookshelves
+    'bookshelf_create'            => 'erstellt Bücherregal',
+    'bookshelf_create_notification'    => 'Das Bücherregal wurde erfolgreich erstellt',
+    'bookshelf_update'                 => 'aktualisiert Bücherregal',
+    'bookshelf_update_notification'    => 'Das Bücherregal wurde erfolgreich aktualisiert',
+    'bookshelf_delete'                 => 'löscht Bücherregal',
+    'bookshelf_delete_notification'    => 'Das Bücherregal wurde erfolgreich gelöscht',
+
+    // Other
+    'commented_on'                => 'kommentiert',
+];
index de9ef91a4d61d345f53e6d5f8bcaa9d8950a9acf..ffe518b931566930720db97784ae5fbd854a6f4f 100644 (file)
@@ -1,47 +1,77 @@
 <?php
-
-// Extends 'de'
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+    'failed' => 'Die eingegebenen Anmeldedaten sind ungültig.',
     'throttle' => 'Zu viele Anmeldeversuche. Bitte versuche es in :seconds Sekunden erneut.',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
+    'sign_up' => 'Registrieren',
+    'log_in' => 'Anmelden',
+    'log_in_with' => 'Anmelden mit :socialDriver',
+    'sign_up_with' => 'Registrieren mit :socialDriver',
+    'logout' => 'Abmelden',
+
+    'name' => 'Name',
+    'username' => 'Benutzername',
+    'email' => 'E-Mail',
+    'password' => 'Passwort',
+    'password_confirm' => 'Passwort best&auml;tigen',
+    'password_hint' => 'Mindestlänge: 7 Zeichen',
+    'forgot_password' => 'Passwort vergessen?',
+    'remember_me' => 'Angemeldet bleiben',
     'ldap_email_hint' => 'Bitte gib eine E-Mail-Adresse ein, um diese mit dem Account zu nutzen.',
+    'create_account' => 'Account registrieren',
+    'already_have_account' => 'Bereits ein Konto erstellt?',
+    'dont_have_account' => 'Noch kein Konto erstellt?',
+    'social_login' => 'Mit Sozialem Netzwerk anmelden',
+    'social_registration' => 'Mit Sozialem Netzwerk registrieren',
+    'social_registration_text' => 'Mit einer dieser Dienste registrieren oder anmelden',
+
+    'register_thanks' => 'Vielen Dank für Ihre Registrierung!',
     'register_confirm' => 'Bitte prüfe Deinen Posteingang und bestätig die Registrierung.',
+    'registrations_disabled' => 'Eine Registrierung ist momentan nicht möglich',
     'registration_email_domain_invalid' => 'Du kannst dich mit dieser E-Mail nicht registrieren.',
     'register_success' => 'Vielen Dank für Deine Registrierung! Die Daten sind gespeichert und Du bist angemeldet.',
 
-    /**
-     * Password Reset
-     */
+
+    // Password Reset
+    'reset_password' => 'Passwort vergessen',
     'reset_password_send_instructions' => 'Bitte gib Deine E-Mail-Adresse ein. Danach erhältst Du eine E-Mail mit einem Link zum Zurücksetzen Deines Passwortes.',
+    'reset_password_send_button' => 'Passwort zurücksetzen',
     'reset_password_sent_success' => 'Eine E-Mail mit dem Link zum Zurücksetzen Deines Passwortes wurde an :email gesendet.',
     'reset_password_success' => 'Dein Passwort wurde erfolgreich zurückgesetzt.',
+    'email_reset_subject' => 'Passwort zurücksetzen für :appName',
     'email_reset_text' => 'Du erhältsts diese E-Mail, weil jemand versucht hat, Dein Passwort zurückzusetzen.',
     'email_reset_not_requested' => 'Wenn Du das nicht warst, brauchst Du nichts weiter zu tun.',
 
-    /**
-     * Email Confirmation
-     */
+
+    // Email Confirmation
     'email_confirm_subject' => 'Bestätige Deine E-Mail-Adresse für :appName',
     'email_confirm_greeting' => 'Danke, dass Du dich für :appName registrierst hast!',
     'email_confirm_text' => 'Bitte bestätige Deine E-Mail-Adresse, indem Du auf die Schaltfläche klickst:',
+    'email_confirm_action' => 'E-Mail-Adresse bestätigen',
     'email_confirm_send_error' => 'Leider konnte die für die Registrierung notwendige E-Mail zur Bestätigung Deine E-Mail-Adresse nicht versandt werden. Bitte kontaktiere den Systemadministrator!',
     'email_confirm_success' => 'Deine E-Mail-Adresse wurde best&auml;tigt!',
     'email_confirm_resent' => 'Bestätigungs-E-Mail wurde erneut versendet, bitte überprüfe Deinen Posteingang.',
+
+    'email_not_confirmed' => 'E-Mail-Adresse ist nicht bestätigt',
     'email_not_confirmed_text' => 'Deine E-Mail-Adresse ist bisher nicht bestätigt.',
     'email_not_confirmed_click_link' => 'Bitte klicke auf den Link in der E-Mail, die Du nach der Registrierung erhalten hast.',
     'email_not_confirmed_resend' => 'Wenn Du die E-Mail nicht erhalten hast, kannst Du die Nachricht erneut anfordern. Fülle hierzu bitte das folgende Formular aus:',
+    'email_not_confirmed_resend_button' => 'Bestätigungs-E-Mail erneut senden',
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
 ];
\ No newline at end of file
index d80fa9dcf29b9657d82b29f26b6b9e4a77899f15..8d9b3aeab1307a8ae70915916232bec2c5c9346c 100644 (file)
@@ -1,9 +1,76 @@
 <?php
-
-// Extends 'de'
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
-    /**
-     * Email Content
-     */
+
+    // Buttons
+    'cancel' => 'Abbrechen',
+    'confirm' => 'Bestätigen',
+    'back' => 'Zurück',
+    'save' => 'Speichern',
+    'continue' => 'Weiter',
+    'select' => 'Auswählen',
+    'toggle_all' => 'Alle umschalten',
+    'more' => 'Mehr',
+
+    // Form Labels
+    'name' => 'Name',
+    'description' => 'Beschreibung',
+    'role' => 'Rolle',
+    'cover_image' => 'Titelbild',
+    'cover_image_description' => 'Das Bild sollte eine Auflösung von 440x250px haben.',
+    
+    // Actions
+    'actions' => 'Aktionen',
+    'view' => 'Anzeigen',
+    'view_all' => 'Alle anzeigen',
+    'create' => 'Anlegen',
+    'update' => 'Aktualisieren',
+    'edit' => 'Bearbeiten',
+    'sort' => 'Sortieren',
+    'move' => 'Verschieben',
+    'copy' => 'Kopieren',
+    'reply' => 'Antworten',
+    'delete' => 'Löschen',
+    'search' => 'Suchen',
+    'search_clear' => 'Suche löschen',
+    'reset' => 'Zurücksetzen',
+    'remove' => 'Entfernen',
+    'add' => 'Hinzufügen',
+
+    // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
+    'sort_name' => 'Name',
+    'sort_created_at' => 'Erstellungsdatum',
+    'sort_updated_at' => 'Aktualisierungsdatum',
+
+    // Misc
+    'deleted_user' => 'Gelöschte Benutzer',
+    'no_activity' => 'Keine Aktivitäten zum Anzeigen',
+    'no_items' => 'Keine Einträge gefunden.',
+    'back_to_top' => 'nach oben',
+    'toggle_details' => 'Details zeigen/verstecken',
+    'toggle_thumbnails' => 'Thumbnails zeigen/verstecken',
+    'details' => 'Details',
+    'grid_view' => 'Gitteransicht',
+    'list_view' => 'Listenansicht',
+    'default' => 'Voreinstellung',
+    'breadcrumb' => 'Breadcrumb',
+
+    // Header
+    'profile_menu' => 'Profile Menu',
+    'view_profile' => 'Profil ansehen',
+    'edit_profile' => 'Profil bearbeiten',
+
+    // Layout tabs
+    'tab_info' => 'Info',
+    'tab_content' => 'Inhalt',
+
+    // Email Content
     'email_action_help' => 'Sollte es beim Anklicken der Schaltfläche ":action_text" Probleme geben, öffne die folgende URL in Deinem Browser:',
-];
\ No newline at end of file
+    'email_rights' => 'Alle Rechte vorbehalten',
+];
index 31cc9ca1be26fd70dffafd46061f0a9fb78ee26f..4d98235a4370b6a243b7ec90ee3cd7553232bc2c 100644 (file)
@@ -1,10 +1,33 @@
 <?php
-
-// Extends 'de'
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
-    /**
-     * Image Manager
-     */
+
+    // Image Manager
+    'image_select' => 'Bild auswählen',
+    'image_all' => 'Alle',
+    'image_all_title' => 'Alle Bilder anzeigen',
+    'image_book_title' => 'Zeige alle Bilder, die in dieses Buch hochgeladen wurden',
+    'image_page_title' => 'Zeige alle Bilder, die auf diese Seite hochgeladen wurden',
+    'image_search_hint' => 'Nach Bildnamen suchen',
+    'image_uploaded' => 'Hochgeladen am :uploadedDate',
+    'image_load_more' => 'Mehr',
+    'image_image_name' => 'Bildname',
+    'image_delete_used' => 'Dieses Bild wird auf den folgenden Seiten benutzt. ',
     'image_delete_confirm' => 'Bitte klicke erneut auf löschen, wenn Du dieses Bild wirklich entfernen möchtest.',
+    'image_select_image' => 'Bild auswählen',
     'image_dropzone' => 'Ziehe Bilder hierher oder klicke hier, um ein Bild auszuwählen',
-];
\ No newline at end of file
+    'images_deleted' => 'Bilder gelöscht',
+    'image_preview' => 'Bildvorschau',
+    'image_upload_success' => 'Bild erfolgreich hochgeladen',
+    'image_update_success' => 'Bilddetails erfolgreich aktualisiert',
+    'image_delete_success' => 'Bild erfolgreich gelöscht',
+    'image_upload_remove' => 'Entfernen',
+
+    // Code Editor
+    'code_editor' => 'Code editieren',
+    'code_language' => 'Code Sprache',
+    'code_content' => 'Code Inhalt',
+    'code_save' => 'Code speichern',
+];
index 1decdd7b78bcd593c160e1d5003670da3c4e603c..1a64f25a7932596d379e27f719586924c11a2363 100644 (file)
 <?php
-
-// Extends 'de'
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
-    /**
-     * Shared
-     */
+
+    // Shared
+    'recently_created' => 'Kürzlich angelegt',
+    'recently_created_pages' => 'Kürzlich angelegte Seiten',
+    'recently_updated_pages' => 'Kürzlich aktualisierte Seiten',
+    'recently_created_chapters' => 'Kürzlich angelegte Kapitel',
+    'recently_created_books' => 'Kürzlich angelegte Bücher',
+    'recently_created_shelves' => 'Kürzlich angelegte Regale',
+    'recently_update' => 'Kürzlich aktualisiert',
+    'recently_viewed' => 'Kürzlich angesehen',
+    'recent_activity' => 'Kürzliche Aktivität',
+    'create_now' => 'Jetzt anlegen',
+    'revisions' => 'Versionen',
+    'meta_revision' => 'Version #:revisionCount',
+    'meta_created' => 'Erstellt: :timeLength',
+    'meta_created_name' => 'Erstellt: :timeLength von :user',
+    'meta_updated' => 'Zuletzt aktualisiert: :timeLength',
+    'meta_updated_name' => 'Zuletzt aktualisiert: :timeLength von :user',
+    'entity_select' => 'Eintrag auswählen',
+    'images' => 'Bilder',
+    'my_recent_drafts' => 'Meine kürzlichen Entwürfe',
+    'my_recently_viewed' => 'Kürzlich von mir angesehen',
     'no_pages_viewed' => 'Du hast bisher keine Seiten angesehen.',
     'no_pages_recently_created' => 'Du hast bisher keine Seiten angelegt.',
     'no_pages_recently_updated' => 'Du hast bisher keine Seiten aktualisiert.',
+    'export' => 'Exportieren',
+    'export_html' => 'HTML-Datei',
+    'export_pdf' => 'PDF-Datei',
+    'export_text' => 'Textdatei',
+
+    // Permissions and restrictions
+    'permissions' => 'Berechtigungen',
+    'permissions_intro' => 'Wenn individuelle Berechtigungen aktiviert werden, überschreiben diese Einstellungen durch Rollen zugewiesene Berechtigungen.',
+    'permissions_enable' => 'Individuelle Berechtigungen aktivieren',
+    'permissions_save' => 'Berechtigungen speichern',
 
-    /**
-     * Shelves
-     */
+    // Search
+    'search_results' => 'Suchergebnisse',
+    'search_total_results_found' => ':count Ergebnis gefunden|:count Ergebnisse gesamt',
+    'search_clear' => 'Filter löschen',
+    'search_no_pages' => 'Keine Seiten gefunden',
+    'search_for_term' => 'Nach :term suchen',
+    'search_more' => 'Mehr Ergebnisse',
+    'search_filters' => 'Filter',
+    'search_content_type' => 'Inhaltstyp',
+    'search_exact_matches' => 'Exakte Treffer',
+    'search_tags' => 'Nach Schlagwort suchen',
+    'search_options' => 'Optionen',
+    'search_viewed_by_me' => 'Schon von mir angesehen',
+    'search_not_viewed_by_me' => 'Noch nicht von mir angesehen',
+    'search_permissions_set' => 'Berechtigungen gesetzt',
+    'search_created_by_me' => 'Von mir erstellt',
+    'search_updated_by_me' => 'Von mir aktualisiert',
+    'search_date_options' => 'Datums Optionen',
+    'search_updated_before' => 'Aktualisiert vor',
+    'search_updated_after' => 'Aktualisiert nach',
+    'search_created_before' => 'Erstellt vor',
+    'search_created_after' => 'Erstellt nach',
+    'search_set_date' => 'Datum auswählen',
+    'search_update' => 'Suche aktualisieren',
+
+    // Shelves
+    'shelf' => 'Regal',
+    'shelves' => 'Regale',
+    'x_shelves' => ':count Regal|:count Regale',
+    'shelves_long' => 'Bücherregal',
+    'shelves_empty' => 'Es wurden noch keine Regale angelegt',
+    'shelves_create' => 'Erzeuge ein Regal',
+    'shelves_popular' => 'Beliebte Regale',
+    'shelves_new' => 'Kürzlich erstellte Regale',
+    'shelves_new_action' => 'Neues Regal',
+    'shelves_popular_empty' => 'Die beliebtesten Regale werden hier angezeigt.',
+    'shelves_new_empty' => 'Die neusten Regale werden hier angezeigt.',
+    'shelves_save' => 'Regal speichern',
+    'shelves_books' => 'Bücher in diesem Regal',
+    'shelves_add_books' => 'Buch zu diesem Regal hinzufügen',
+    'shelves_drag_books' => 'Bücher hier hin ziehen um sie dem Regal hinzuzufügen',
+    'shelves_empty_contents' => 'Diesem Regal sind keine Bücher zugewiesen',
+    'shelves_edit_and_assign' => 'Regal bearbeiten um Bücher hinzuzufügen',
+    'shelves_edit_named' => 'Bücherregal :name bearbeiten',
+    'shelves_edit' => 'Bücherregal bearbeiten',
+    'shelves_delete' => 'Bücherregal löschen',
+    'shelves_delete_named' => 'Bücherregal :name löschen',
     'shelves_delete_explain' => "Du bist im Begriff das Bücherregal mit dem Namen ':name' zu löschen. Enthaltene Bücher werden nicht gelöscht.",
     'shelves_delete_confirmation' => 'Bist du sicher, dass du dieses Bücherregal löschen willst?',
+    'shelves_permissions' => 'Regal-Berechtigungen',
+    'shelves_permissions_updated' => 'Regal-Berechtigungen aktualisiert',
+    'shelves_permissions_active' => 'Regal-Berechtigungen aktiv',
+    'shelves_copy_permissions_to_books' => 'Kopiere die Berechtigungen zum Buch',
+    'shelves_copy_permissions' => 'Berechtigungen kopieren',
     'shelves_copy_permissions_explain' => 'Hiermit werden die Berechtigungen des aktuellen Regals auf alle enthaltenen Bücher übertragen. Überprüfe vor der Aktivierung, ob alle Berechtigungsänderungen am aktuellen Regal gespeichert wurden.',
-    
-    /**
-     * Books
-     */
+    'shelves_copy_permission_success' => 'Regal-Berechtigungen wurden zu :count Büchern kopiert',
+
+    // Books
+    'book' => 'Buch',
+    'books' => 'Bücher',
+    'x_books' => ':count Buch|:count Bücher',
+    'books_empty' => 'Keine Bücher vorhanden',
+    'books_popular' => 'Beliebte Bücher',
+    'books_recent' => 'Kürzlich angesehene Bücher',
+    'books_new' => 'Neue Bücher',
+    'books_new_action' => 'Neues Buch',
+    'books_popular_empty' => 'Die beliebtesten Bücher werden hier angezeigt.',
+    'books_new_empty' => 'Die neusten Bücher werden hier angezeigt.',
+    'books_create' => 'Neues Buch erstellen',
+    'books_delete' => 'Buch löschen',
+    'books_delete_named' => 'Buch ":bookName" löschen',
+    'books_delete_explain' => 'Das Buch ":bookName" wird gelöscht und alle zugehörigen Kapitel und Seiten entfernt.',
     'books_delete_confirmation' => 'Bist Du sicher, dass Du dieses Buch löschen möchtest?',
+    'books_edit' => 'Buch bearbeiten',
+    'books_edit_named' => 'Buch ":bookName" bearbeiten',
+    'books_form_book_name' => 'Name des Buches',
+    'books_save' => 'Buch speichern',
+    'books_permissions' => 'Buch-Berechtigungen',
+    'books_permissions_updated' => 'Buch-Berechtigungen aktualisiert',
+    'books_empty_contents' => 'Es sind noch keine Seiten oder Kapitel zu diesem Buch hinzugefügt worden.',
+    'books_empty_create_page' => 'Neue Seite anlegen',
+    'books_empty_sort_current_book' => 'Aktuelles Buch sortieren',
+    'books_empty_add_chapter' => 'Neues Kapitel hinzufügen',
+    'books_permissions_active' => 'Buch-Berechtigungen aktiv',
+    'books_search_this' => 'Dieses Buch durchsuchen',
+    'books_navigation' => 'Buchnavigation',
+    'books_sort' => 'Buchinhalte sortieren',
+    'books_sort_named' => 'Buch ":bookName" sortieren',
+    'books_sort_name' => 'Sortieren nach Namen',
+    'books_sort_created' => 'Sortieren nach Erstellungsdatum',
+    'books_sort_updated' => 'Sortieren nach Aktualisierungsdatum',
+    'books_sort_chapters_first' => 'Kapitel zuerst',
+    'books_sort_chapters_last' => 'Kapitel zuletzt',
+    'books_sort_show_other' => 'Andere Bücher anzeigen',
+    'books_sort_save' => 'Neue Reihenfolge speichern',
 
-    /**
-     * Chapters
-     */
+    // Chapters
+    'chapter' => 'Kapitel',
+    'chapters' => 'Kapitel',
+    'x_chapters' => ':count Kapitel',
+    'chapters_popular' => 'Beliebte Kapitel',
+    'chapters_new' => 'Neues Kapitel',
+    'chapters_create' => 'Neues Kapitel anlegen',
+    'chapters_delete' => 'Kapitel entfernen',
+    'chapters_delete_named' => 'Kapitel ":chapterName" entfernen',
+    'chapters_delete_explain' => 'Das Kapitel ":chapterName" wird gelöscht und alle zugehörigen Seiten dem übergeordneten Buch zugeordnet.',
     'chapters_delete_confirm' => 'Bist Du sicher, dass Du dieses Kapitel löschen möchtest?',
+    'chapters_edit' => 'Kapitel bearbeiten',
+    'chapters_edit_named' => 'Kapitel ":chapterName" bearbeiten',
+    'chapters_save' => 'Kapitel speichern',
+    'chapters_move' => 'Kapitel verschieben',
+    'chapters_move_named' => 'Kapitel ":chapterName" verschieben',
+    'chapter_move_success' => 'Das Kapitel wurde in das Buch ":bookName" verschoben.',
+    'chapters_permissions' => 'Kapitel-Berechtigungen',
+    'chapters_empty' => 'Aktuell sind keine Kapitel diesem Buch hinzugefügt worden.',
+    'chapters_permissions_active' => 'Kapitel-Berechtigungen aktiv',
+    'chapters_permissions_success' => 'Kapitel-Berechtigungenen aktualisisert',
+    'chapters_search_this' => 'Dieses Kapitel durchsuchen',
 
-    /**
-     * Pages
-     */
+    // Pages
+    'page' => 'Seite',
+    'pages' => 'Seiten',
+    'x_pages' => ':count Seite|:count Seiten',
+    'pages_popular' => 'Beliebte Seiten',
+    'pages_new' => 'Neue Seite',
+    'pages_attachments' => 'Anhänge',
+    'pages_navigation' => 'Seitennavigation',
+    'pages_delete' => 'Seite löschen',
+    'pages_delete_named' => 'Seite ":pageName" löschen',
+    'pages_delete_draft_named' => 'Seitenentwurf von ":pageName" löschen',
+    'pages_delete_draft' => 'Seitenentwurf löschen',
+    'pages_delete_success' => 'Seite gelöscht',
+    'pages_delete_draft_success' => 'Seitenentwurf gelöscht',
     'pages_delete_confirm' => 'Bist Du sicher, dass Du diese Seite löschen möchtest?',
     'pages_delete_draft_confirm' => 'Bist Du sicher, dass Du diesen Seitenentwurf löschen möchtest?',
+    'pages_editing_named' => 'Seite ":pageName" bearbeiten',
+    'pages_edit_draft_options' => 'Draft Options',
+    'pages_edit_save_draft' => 'Entwurf speichern',
+    'pages_edit_draft' => 'Seitenentwurf bearbeiten',
+    'pages_editing_draft' => 'Seitenentwurf bearbeiten',
+    'pages_editing_page' => 'Seite bearbeiten',
+    'pages_edit_draft_save_at' => 'Entwurf gespeichert um ',
+    'pages_edit_delete_draft' => 'Entwurf löschen',
+    'pages_edit_discard_draft' => 'Entwurf verwerfen',
+    'pages_edit_set_changelog' => 'Änderungsprotokoll hinzufügen',
     'pages_edit_enter_changelog_desc' => 'Bitte gib eine kurze Zusammenfassung Deiner Änderungen ein',
+    'pages_edit_enter_changelog' => 'Änderungsprotokoll eingeben',
+    'pages_save' => 'Seite speichern',
+    'pages_title' => 'Seitentitel',
+    'pages_name' => 'Seitenname',
+    'pages_md_editor' => 'Redakteur',
+    'pages_md_preview' => 'Vorschau',
+    'pages_md_insert_image' => 'Bild einfügen',
+    'pages_md_insert_link' => 'Link zu einem Objekt einfügen',
+    'pages_md_insert_drawing' => 'Zeichnung einfügen',
+    'pages_not_in_chapter' => 'Seite ist in keinem Kapitel',
+    'pages_move' => 'Seite verschieben',
+    'pages_move_success' => 'Seite nach ":parentName" verschoben',
+    'pages_copy' => 'Seite kopieren',
+    'pages_copy_desination' => 'Ziel',
+    'pages_copy_success' => 'Seite erfolgreich kopiert',
+    'pages_permissions' => 'Seiten Berechtigungen',
+    'pages_permissions_success' => 'Seiten Berechtigungen aktualisiert',
+    'pages_revision' => 'Version',
+    'pages_revisions' => 'Seitenversionen',
+    'pages_revisions_named' => 'Seitenversionen von ":pageName"',
+    'pages_revision_named' => 'Seitenversion von ":pageName"',
+    'pages_revisions_created_by' => 'Erstellt von',
+    'pages_revisions_date' => 'Versionsdatum',
+    'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
+    'pages_revisions_changelog' => 'Änderungsprotokoll',
+    'pages_revisions_changes' => 'Änderungen',
+    'pages_revisions_current' => 'Aktuelle Version',
+    'pages_revisions_preview' => 'Vorschau',
+    'pages_revisions_restore' => 'Wiederherstellen',
+    'pages_revisions_none' => 'Diese Seite hat keine älteren Versionen.',
+    'pages_copy_link' => 'Link kopieren',
+    'pages_edit_content_link' => 'Inhalt bearbeiten',
+    'pages_permissions_active' => 'Seiten-Berechtigungen aktiv',
+    'pages_initial_revision' => 'Erste Veröffentlichung',
+    'pages_initial_name' => 'Neue Seite',
     'pages_editing_draft_notification' => 'Du bearbeitest momenten einen Entwurf, der zuletzt :timeDiff gespeichert wurde.',
+    'pages_draft_edited_notification' => 'Diese Seite wurde seit diesem Zeitpunkt verändert. Wir empfehlen Ihnen, diesen Entwurf zu verwerfen.',
     'pages_draft_edit_active' => [
         'start_a' => ':count Benutzer bearbeiten derzeit diese Seite.',
         'start_b' => ':userName bearbeitet jetzt diese Seite.',
@@ -40,25 +232,83 @@ return [
         'time_b' => 'in den letzten :minCount Minuten',
         'message' => ':start :time. Achte darauf, keine Änderungen von anderen Benutzern zu überschreiben!',
     ],
+    'pages_draft_discarded' => 'Entwurf verworfen. Der aktuelle Seiteninhalt wurde geladen.',
+    'pages_specific' => 'Specific Page',
+    'pages_is_template' => 'Page Template',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
+    'page_tags' => 'Seiten-Schlagwörter',
+    'chapter_tags' => 'Kapitel-Schlagwörter',
+    'book_tags' => 'Buch-Schlagwörter',
+    'shelf_tags' => 'Regal-Schlagwörter',
+    'tag' => 'Schlagwort',
+    'tags' =>  'Schlagwörter',
+    'tag_name' =>  'Tag Name',
+    'tag_value' => 'Inhalt (Optional)',
     'tags_explain' => "Füge Schlagwörter hinzu, um ihren Inhalt zu kategorisieren.\nDu kannst einen erklärenden Inhalt hinzufügen, um eine genauere Unterteilung vorzunehmen.",
+    'tags_add' => 'Weiteres Schlagwort hinzufügen',
+    'tags_remove' => 'Remove this tag',
+    'attachments' => 'Anhänge',
     'attachments_explain' => 'Du kannst auf Deiner Seite Dateien hochladen oder Links hinzufügen. Diese werden in der Seitenleiste angezeigt.',
+    'attachments_explain_instant_save' => 'Änderungen werden direkt gespeichert.',
+    'attachments_items' => 'Angefügte Elemente',
+    'attachments_upload' => 'Datei hochladen',
+    'attachments_link' => 'Link hinzufügen',
+    'attachments_set_link' => 'Link setzen',
     'attachments_delete_confirm' => 'Klicke erneut auf löschen, um diesen Anhang zu entfernen.',
     'attachments_dropzone' => 'Ziehe Dateien hierher oder klicke hier, um eine Datei auszuwählen',
+    'attachments_no_files' => 'Es wurden bisher keine Dateien hochgeladen.',
     'attachments_explain_link' => 'Wenn Du keine Datei hochladen möchtest, kannst Du stattdessen einen Link hinzufügen. Dieser Link kann auf eine andere Seite oder eine Datei im Internet verweisen.',
+    'attachments_link_name' => 'Link-Name',
+    'attachment_link' => 'Link zum Anhang',
+    'attachments_link_url' => 'Link zu einer Datei',
+    'attachments_link_url_hint' => 'URL einer Seite oder Datei',
+    'attach' => 'Hinzufügen',
+    'attachments_edit_file' => 'Datei bearbeiten',
+    'attachments_edit_file_name' => 'Dateiname',
     'attachments_edit_drop_upload' => 'Ziehe Dateien hierher, um diese hochzuladen und zu überschreiben',
+    'attachments_order_updated' => 'Reihenfolge der Anhänge aktualisiert',
+    'attachments_updated_success' => 'Anhangdetails aktualisiert',
+    'attachments_deleted' => 'Anhang gelöscht',
+    'attachments_file_uploaded' => 'Datei erfolgreich hochgeladen',
+    'attachments_file_updated' => 'Datei erfolgreich aktualisiert',
+    'attachments_link_attached' => 'Link erfolgreich der Seite hinzugefügt',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
+
+    // Profile View
+    'profile_user_for_x' => 'Benutzer seit :time',
+    'profile_created_content' => 'Erstellte Inhalte',
+    'profile_not_created_pages' => ':userName hat noch keine Seiten erstellt.',
+    'profile_not_created_chapters' => ':userName hat noch keine Kapitel erstellt.',
+    'profile_not_created_books' => ':userName hat noch keine Bücher erstellt.',
+    'profile_not_created_shelves' => ':userName hat noch keine Regale erstellt.',
 
-    /**
-     * Comments
-     */
+    // Comments
+    'comment' => 'Kommentar',
+    'comments' => 'Kommentare',
+    'comment_add' => 'Kommentieren',
     'comment_placeholder' => 'Gib hier Deine Kommentare ein (Markdown unterstützt)',
+    'comment_count' => '{0} Keine Kommentare|{1} 1 Kommentar|[2,*] :count Kommentare',
+    'comment_save' => 'Kommentar speichern',
+    'comment_saving' => 'Kommentar wird gespeichert...',
+    'comment_deleting' => 'Kommentar wird gelöscht...',
+    'comment_new' => 'Neuer Kommentar',
+    'comment_created' => ':createDiff kommentiert',
+    'comment_updated' => ':updateDiff aktualisiert von :username',
+    'comment_deleted_success' => 'Kommentar gelöscht',
+    'comment_created_success' => 'Kommentar hinzugefügt',
+    'comment_updated_success' => 'Kommentar aktualisiert',
     'comment_delete_confirm' => 'Möchtst Du diesen Kommentar wirklich löschen?',
+    'comment_in_reply_to' => 'Antwort auf :commentId',
 
-    /**
-     * Revision
-     */
+    // Revision
     'revision_delete_confirm' => 'Bist Du sicher, dass Du diese Revision löschen möchtest?',
-];
+    'revision_restore_confirm' => 'Sind Sie sicher, dass Sie diese Revision wiederherstellen wollen? Der aktuelle Seiteninhalt wird ersetzt.',
+    'revision_delete_success' => 'Revision gelöscht',
+    'revision_cannot_delete_latest' => 'Die letzte Version kann nicht gelöscht werden.'
+];
\ No newline at end of file
index 420c35c8d5bd849f3195020d25ed78bc0bbf7c09..e6235015660218d3c65faa587c90246ead863d1a 100644 (file)
@@ -1,33 +1,80 @@
 <?php
-
-// Extends 'de'
+/**
+ * Text shown in error messaging.
+ */
 return [
-    // Pages
+
+    // Permissions
     'permission' => 'Du hast keine Berechtigung, auf diese Seite zuzugreifen.',
     'permissionJson' => 'Du hast keine Berechtigung, die angeforderte Aktion auszuführen.',
 
     // Auth
+    'saml_already_logged_in' => 'Du bist bereits angemeldet',
+    'error_user_exists_different_creds' => 'Ein Benutzer mit der E-Mail-Adresse :email ist bereits mit anderen Anmeldedaten registriert.',
     'email_already_confirmed' => 'Die E-Mail-Adresse ist bereits bestätigt. Bitte melde dich an.',
     'email_confirmation_invalid' => 'Der Bestätigungslink ist nicht gültig oder wurde bereits verwendet. Bitte registriere dich erneut.',
-    'saml_already_logged_in' => 'Du bist bereits angemeldet',
+
     'social_account_in_use' => 'Dieses :socialAccount-Konto wird bereits verwendet. Bitte melde dich mit dem :socialAccount-Konto an.',
     'social_account_email_in_use' => 'Die E-Mail-Adresse ":email" ist bereits registriert. Wenn Du bereits registriert bist, kannst Du Dein :socialAccount-Konto in Deinen Profil-Einstellungen verknüpfen.',
+    'social_account_existing' => 'Dieses :socialAccount-Konto ist bereits mit Ihrem Profil verknüpft.',
+    'social_account_already_used_existing' => 'Dieses :socialAccount-Konto wird bereits von einem anderen Benutzer verwendet.',
     'social_account_not_used' => 'Dieses :socialAccount-Konto ist bisher keinem Benutzer zugeordnet. Du kannst das in Deinen Profil-Einstellungen tun.',
     'social_account_register_instructions' => 'Wenn Du bisher kein Social-Media Konto besitzt, kannst Du ein solches Konto mit der :socialAccount Option anlegen.',
+    'social_driver_not_found' => 'Treiber für Social-Media-Konten nicht gefunden',
+    'social_driver_not_configured' => 'Ihr :socialAccount-Konto ist nicht korrekt konfiguriert.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => 'Die Datei kann nicht in den angegebenen Pfad :filePath hochgeladen werden. Stelle sicher, dass dieser Ordner auf dem Server beschreibbar ist.',
+    'cannot_get_image_from_url' => 'Bild konnte nicht von der URL :url geladen werden.',
     'cannot_create_thumbs' => 'Der Server kann keine Vorschau-Bilder erzeugen. Bitte prüfe, ob die GD PHP-Erweiterung installiert ist.',
     'server_upload_limit' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuche es mit einer kleineren Datei.',
+    'uploaded'  => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuchen Sie es mit einer kleineren Datei.',
+    'image_upload_error' => 'Beim Hochladen des Bildes trat ein Fehler auf.',
+    'image_upload_type_error' => 'Der Bildtyp der hochgeladenen Datei ist ungültig.',
+    'file_upload_timeout' => 'Der Upload der Datei ist abgelaufen.',
+
+    // Attachments
+    'attachment_page_mismatch' => 'Die Seite stimmte nach dem Hochladen des Anhangs nicht überein.',
+    'attachment_not_found' => 'Anhang konnte nicht gefunden werden.',
 
     // Pages
     'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stelle sicher, dass Du mit dem Internet verbunden bist, bevor Du den Entwurf dieser Seite speicherst.',
     'page_custom_home_deletion' => 'Eine als Startseite gesetzte Seite kann nicht gelöscht werden.',
 
+    // Entities
+    'entity_not_found' => 'Eintrag nicht gefunden',
+    'bookshelf_not_found' => 'Regal nicht gefunden',
+    'book_not_found' => 'Buch nicht gefunden',
+    'page_not_found' => 'Seite nicht gefunden',
+    'chapter_not_found' => 'Kapitel nicht gefunden',
+    'selected_book_not_found' => 'Das gewählte Buch wurde nicht gefunden.',
+    'selected_book_chapter_not_found' => 'Das gewählte Buch oder Kapitel wurde nicht gefunden.',
+    'guests_cannot_save_drafts' => 'Gäste können keine Entwürfe speichern',
+
     // Users
     'users_cannot_delete_only_admin' => 'Du kannst den einzigen Administrator nicht löschen.',
     'users_cannot_delete_guest' => 'Du kannst den Gast-Benutzer nicht löschen',
 
+    // Roles
+    'role_cannot_be_edited' => 'Diese Rolle kann nicht bearbeitet werden.',
+    'role_system_cannot_be_deleted' => 'Dies ist eine Systemrolle und kann nicht gelöscht werden',
+    'role_registration_default_cannot_delete' => 'Diese Rolle kann nicht gelöscht werden, solange sie als Standardrolle für neue Registrierungen gesetzt ist',
+    'role_cannot_remove_only_admin' => 'Dieser Benutzer ist der einzige Benutzer, welchem die Administratorrolle zugeordnet ist. Ordnen Sie die Administratorrolle einem anderen Benutzer zu, bevor Sie versuchen, sie hier zu entfernen.',
+
+    // Comments
+    'comment_list' => 'Beim Abrufen der Kommentare ist ein Fehler aufgetreten.',
+    'cannot_add_comment_to_draft' => 'Du kannst keine Kommentare zu einem Entwurf hinzufügen.',
+    'comment_add' => 'Beim Hinzufügen des Kommentars ist ein Fehler aufgetreten.',
+    'comment_delete' => 'Beim Löschen des Kommentars ist ein Fehler aufgetreten.',
+    'empty_comment' => 'Kann keinen leeren Kommentar hinzufügen',
+
     // Error pages
+    '404_page_not_found' => 'Seite nicht gefunden',
     'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Du angefordert hast, wurde nicht gefunden.',
+    'return_home' => 'Zurück zur Startseite',
+    'error_occurred' => 'Es ist ein Fehler aufgetreten',
+    'app_down' => ':appName befindet sich aktuell im Wartungsmodus.',
+    'back_soon' => 'Wir werden so schnell wie möglich wieder online sein.',
+
 ];
index c82c9e0c449673b88af05595e27da895900530c3..5777671378d589b75becec12ade5e4dec86ce286 100644 (file)
@@ -1,6 +1,12 @@
 <?php
-
-// Extends 'de'
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
-    //
-];
\ No newline at end of file
+
+    'previous' => '&laquo; Vorherige',
+    'next'     => 'Nächste &raquo;',
+
+];
index c82c9e0c449673b88af05595e27da895900530c3..74149a7eda09f40a2f07d84ff535584a6882f8de 100644 (file)
@@ -1,6 +1,15 @@
 <?php
-
-// Extends 'de'
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
-    //
-];
\ No newline at end of file
+
+    'password' => 'Passwörter müssen aus mindestens sechs Zeichen bestehen und mit der eingegebenen Wiederholung übereinstimmen.',
+    'user' => "Es wurde kein Benutzer mit dieser E-Mail-Adresse gefunden.",
+    'token' => 'Dieser Link zum Zurücksetzen des Passwortes ist ungültig!',
+    'sent' => 'Der Link zum Zurücksetzen Ihres Passwortes wurde Ihnen per E-Mail zugesendet.',
+    'reset' => 'Ihr Passwort wurde zurückgesetzt!',
+
+];
index c8f5a1b10a849e99a36146bd41aa1296231205ed..ed8d8a11fc76aa4f873d12eebf617756bbc9804a 100644 (file)
 <?php
-
-// Extends 'de'
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
 
-    /**
-     * App settings
-     */
+    // Common Messages
+    'settings' => 'Einstellungen',
+    'settings_save' => 'Einstellungen speichern',
+    'settings_save_success' => 'Einstellungen gespeichert',
+
+    // App Settings
+    'app_customization' => 'Personalisierung',
+    'app_features_security' => 'Funktionen & Sicherheit',
+    'app_name' => 'Anwendungsname',
+    'app_name_desc' => 'Dieser Name wird im Header und in E-Mails angezeigt.',
+    'app_name_header' => 'Anwendungsname im Header anzeigen?',
+    'app_public_access' => 'Öffentlicher Zugriff',
+    'app_public_access_desc' => 'Wenn Sie diese Option aktivieren, können Besucher, die nicht angemeldet sind, auf Inhalte in Ihrer BookStack-Instanz zugreifen.',
+    'app_public_access_desc_guest' => 'Der Zugang für öffentliche Besucher kann über den Benutzer "Guest" gesteuert werden.',
+    'app_public_access_toggle' => 'Öffentlichen Zugriff erlauben',
+    'app_public_viewing' => 'Öffentliche Ansicht erlauben?',
+    'app_secure_images' => 'Erhöhte Sicherheit für hochgeladene Bilder aktivieren?',
+    'app_secure_images_toggle' => 'Enable higher security image uploads',
+    'app_secure_images_desc' => 'Aus Leistungsgründen sind alle Bilder öffentlich sichtbar. Diese Option fügt zufällige, schwer zu eratene, Zeichenketten zu Bild-URLs hinzu. Stellen sie sicher, dass Verzeichnisindizes deaktiviert sind, um einen einfachen Zugriff zu verhindern.',
+    'app_editor' => 'Seiteneditor',
     'app_editor_desc' => 'Wähle den Editor aus, der von allen Benutzern genutzt werden soll, um Seiten zu editieren.',
-    'app_primary_color_desc' => "Dies sollte ein HEX Wert sein.\nWenn Du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt.",
+    'app_custom_html' => 'Benutzerdefinierter HTML <head> Inhalt',
+    'app_custom_html_desc' => 'Jeder Inhalt, der hier hinzugefügt wird, wird am Ende der <head> Sektion jeder Seite eingefügt. Diese kann praktisch sein, um CSS Styles anzupassen oder Analytics-Code hinzuzufügen.',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
+    'app_logo' => 'Anwendungslogo',
+    'app_logo_desc' => 'Dieses Bild sollte 43px hoch sein.
+Größere Bilder werden verkleinert.',
+    'app_primary_color' => 'Primäre Anwendungsfarbe',
+    'app_primary_color_desc' => 'Dies sollte ein HEX Wert sein.
+Wenn Du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt.',
+    'app_homepage' => 'Startseite der Anwendung',
     'app_homepage_desc' => 'Wähle eine Seite als Startseite aus, die statt der Standardansicht angezeigt werden soll. Seitenberechtigungen werden für die ausgewählten Seiten ignoriert.',
-    'app_homepage_books' => 'Oder wähle die Buch-Übersicht als Startseite. Das wird die Seiten-Auswahl überschreiben.',
+    'app_homepage_select' => 'Wählen Sie eine Seite aus',
+    'app_disable_comments' => 'Kommentare deaktivieren',
+    'app_disable_comments_toggle' => 'Kommentare deaktivieren',
+    'app_disable_comments_desc' => 'Deaktiviert Kommentare über alle Seiten in der Anwendung. Vorhandene Kommentare werden nicht angezeigt.',
 
-    /**
-     * Maintenance settings
-     */
-    'maint_image_cleanup_desc' => 'Überprüft Seiten- und Versionsinhalte auf ungenutzte und mehrfach vorhandene Bilder. Erstelle vor dem Start ein Backup Deiner Datenbank und Bilder.',
+    // Registration Settings
+    'reg_settings' => 'Registrierungseinstellungen',
+    'reg_enable' => 'Registrierung erlauben?',
+    'reg_enable_toggle' => 'Registrierung erlauben',
+    'reg_enable_desc' => 'Wenn die Registrierung erlaubt ist, kann sich der Benutzer als Anwendungsbenutzer anmelden. Bei der Registrierung erhält er eine einzige, voreingestellte Benutzerrolle.',
+    'reg_default_role' => 'Standard-Benutzerrolle nach Registrierung',
+    'reg_email_confirmation' => 'Bestätigung per E-Mail',
+    'reg_email_confirmation_toggle' => 'Bestätigung per E-Mail erforderlich',
+    'reg_confirm_email_desc' => 'Falls die Einschränkung für Domains genutzt wird, ist die Bestätigung per E-Mail zwingend erforderlich und der untenstehende Wert wird ignoriert.',
+    'reg_confirm_restrict_domain' => 'Registrierung auf bestimmte Domains einschränken',
+    'reg_confirm_restrict_domain_desc' => 'Fügen sie eine durch Komma getrennte Liste von Domains hinzu, auf die die Registrierung eingeschränkt werden soll. Benutzern wird eine E-Mail gesendet, um ihre E-Mail Adresse zu bestätigen, bevor sie diese Anwendung nutzen können.
+Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung ändern.',
+    'reg_confirm_restrict_domain_placeholder' => 'Keine Einschränkung gesetzt',
+
+    // Maintenance settings
+    'maint' => 'Wartung',
+    'maint_image_cleanup' => 'Bilder bereinigen',
+    'maint_image_cleanup_desc' => "Überprüft Seiten- und Versionsinhalte auf ungenutzte und mehrfach vorhandene Bilder. Erstelle vor dem Start ein Backup Deiner Datenbank und Bilder.",
+    'maint_image_cleanup_ignore_revisions' => 'Bilder in Versionen ignorieren',
+    'maint_image_cleanup_run' => 'Reinigung starten',
     'maint_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchtest Du diese Bilder löschen?',
+    'maint_image_cleanup_success' => ':count eventuell unbenutze Bilder wurden gefunden und gelöscht.',
+    'maint_image_cleanup_nothing_found' => 'Keine unbenutzen Bilder gefunden. Nichts zu löschen!',
 
-    /**
-     * Role settings
-     */
+    // Role Settings
+    'roles' => 'Rollen',
+    'role_user_roles' => 'Benutzer-Rollen',
+    'role_create' => 'Neue Rolle anlegen',
+    'role_create_success' => 'Rolle erfolgreich angelegt',
+    'role_delete' => 'Rolle löschen',
     'role_delete_confirm' => 'Du möchtest die Rolle ":roleName" löschen.',
     'role_delete_users_assigned' => 'Diese Rolle ist :userCount Benutzern zugeordnet. Du kannst unten eine neue Rolle auswählen, die Du diesen Benutzern zuordnen möchtest.',
+    'role_delete_no_migration' => "Den Benutzern keine andere Rolle zuordnen",
     'role_delete_sure' => 'Bist Du sicher, dass Du diese Rolle löschen möchtest?',
+    'role_delete_success' => 'Rolle erfolgreich gelöscht',
+    'role_edit' => 'Rolle bearbeiten',
+    'role_details' => 'Rollendetails',
+    'role_name' => 'Rollenname',
+    'role_desc' => 'Kurzbeschreibung der Rolle',
+    'role_external_auth_id' => 'Externe Authentifizierungs-IDs',
+    'role_system' => 'System-Berechtigungen',
+    'role_manage_users' => 'Benutzer verwalten',
+    'role_manage_roles' => 'Rollen und Rollen-Berechtigungen verwalten',
+    'role_manage_entity_permissions' => 'Alle Buch-, Kapitel- und Seiten-Berechtigungen verwalten',
+    'role_manage_own_entity_permissions' => 'Nur Berechtigungen eigener Bücher, Kapitel und Seiten verwalten',
+    'role_manage_page_templates' => 'Manage page templates',
+    'role_manage_settings' => 'Globaleinstellungen verwalten',
+    'role_asset' => 'Berechtigungen',
+    'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungenen.',
+    'role_asset_admins' => 'Administratoren erhalten automatisch Zugriff auf alle Inhalte, aber diese Optionen können Oberflächenoptionen ein- oder ausblenden.',
+    'role_all' => 'Alle',
+    'role_own' => 'Eigene',
+    'role_controlled_by_asset' => 'Berechtigungen werden vom Uploadziel bestimmt',
+    'role_save' => 'Rolle speichern',
+    'role_update_success' => 'Rolle erfolgreich gespeichert',
+    'role_users' => 'Dieser Rolle zugeordnete Benutzer',
+    'role_users_none' => 'Bisher sind dieser Rolle keine Benutzer zugeordnet',
 
-    /**
-     * Users
-     */
+    // Users
+    'users' => 'Benutzer',
+    'user_profile' => 'Benutzerprofil',
+    'users_add_new' => 'Benutzer hinzufügen',
+    'users_search' => 'Benutzer suchen',
+    'users_details' => 'Benutzerdetails',
+    'users_details_desc' => 'Legen Sie für diesen Benutzer einen Anzeigenamen und eine E-Mail-Adresse fest. Die E-Mail-Adresse wird bei der Anmeldung verwendet.',
+    'users_details_desc_no_email' => 'Legen Sie für diesen Benutzer einen Anzeigenamen fest, damit andere ihn erkennen können.',
+    'users_role' => 'Benutzerrollen',
+    'users_role_desc' => 'Wählen Sie aus, welchen Rollen dieser Benutzer zugeordnet werden soll. Wenn ein Benutzer mehreren Rollen zugeordnet ist, werden die Berechtigungen dieser Rollen gestapelt und er erhält alle Fähigkeiten der zugewiesenen Rollen.',
+    'users_password' => 'Benutzerpasswort',
+    'users_password_desc' => 'Legen Sie ein Passwort fest, mit dem Sie sich anmelden möchten. Diese muss mindestens 5 Zeichen lang sein.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
+    'users_external_auth_id' => 'Externe Authentifizierungs-ID',
+    'users_external_auth_id_desc' => 'Dies ist die ID, die verwendet wird, um diesen Benutzer bei der Kommunikation mit Ihrem LDAP-System abzugleichen.',
     'users_password_warning' => 'Fülle die folgenden Felder nur aus, wenn Du Dein Passwort ändern möchtest:',
+    'users_system_public' => 'Dieser Benutzer repräsentiert alle unangemeldeten Benutzer, die diese Seite betrachten. Er kann nicht zum Anmelden benutzt werden, sondern wird automatisch zugeordnet.',
+    'users_delete' => 'Benutzer löschen',
+    'users_delete_named' => 'Benutzer ":userName" löschen',
+    'users_delete_warning' => 'Der Benutzer ":userName" wird aus dem System gelöscht.',
     'users_delete_confirm' => 'Bist Du sicher, dass Du diesen Benutzer löschen möchtest?',
+    'users_delete_success' => 'Benutzer erfolgreich gelöscht.',
+    'users_edit' => 'Benutzer bearbeiten',
+    'users_edit_profile' => 'Profil bearbeiten',
+    'users_edit_success' => 'Benutzer erfolgreich aktualisisert',
+    'users_avatar' => 'Benutzer-Bild',
+    'users_avatar_desc' => 'Das Bild sollte eine Auflösung von 256x256px haben.',
+    'users_preferred_language' => 'Bevorzugte Sprache',
+    'users_preferred_language_desc' => 'Diese Option ändert die Sprache, die für die Benutzeroberfläche der Anwendung verwendet wird. Dies hat keinen Einfluss auf von Benutzern erstellte Inhalte.',
+    'users_social_accounts' => 'Social-Media Konten',
     'users_social_accounts_info' => 'Hier kannst Du andere Social-Media-Konten für eine schnellere und einfachere Anmeldung verknüpfen. Wenn Du ein Social-Media Konto löschst, bleibt der Zugriff erhalten. Entferne in diesem Falle die Berechtigung in Deinen Profil-Einstellungen des verknüpften Social-Media-Kontos.',
+    'users_social_connect' => 'Social-Media-Konto verknüpfen',
+    'users_social_disconnect' => 'Social-Media-Konto lösen',
+    'users_social_connected' => ':socialAccount-Konto wurde erfolgreich mit dem Profil verknüpft.',
+    'users_social_disconnected' => ':socialAccount-Konto wurde erfolgreich vom Profil gelöst.',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index c82c9e0c449673b88af05595e27da895900530c3..1cf2176d5283129f916a755b3df083f52a1dff3e 100644 (file)
@@ -1,6 +1,114 @@
 <?php
-
-// Extends 'de'
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
-    //
-];
\ No newline at end of file
+
+    // Standard laravel validation lines
+    'accepted'             => ':attribute muss akzeptiert werden.',
+    'active_url'           => ':attribute ist keine valide URL.',
+    'after'                => ':attribute muss ein Datum nach :date sein.',
+    'alpha'                => ':attribute kann nur Buchstaben enthalten.',
+    'alpha_dash'           => ':attribute kann nur Buchstaben, Zahlen und Bindestriche enthalten.',
+    'alpha_num'            => ':attribute kann nur Buchstaben und Zahlen enthalten.',
+    'array'                => ':attribute muss ein Array sein.',
+    'before'               => ':attribute muss ein Datum vor :date sein.',
+    'between'              => [
+        'numeric' => ':attribute muss zwischen :min und :max liegen.',
+        'file'    => ':attribute muss zwischen :min und :max Kilobytes groß sein.',
+        'string'  => ':attribute muss zwischen :min und :max Zeichen lang sein.',
+        'array'   => ':attribute muss zwischen :min und :max Elemente enthalten.',
+    ],
+    'boolean'              => ':attribute Feld muss wahr oder falsch sein.',
+    'confirmed'            => ':attribute stimmt nicht überein.',
+    'date'                 => ':attribute ist kein valides Datum.',
+    'date_format'          => ':attribute entspricht nicht dem Format :format.',
+    'different'            => ':attribute und :other müssen unterschiedlich sein.',
+    'digits'               => ':attribute muss :digits Stellen haben.',
+    'digits_between'       => ':attribute muss zwischen :min und :max Stellen haben.',
+    'email'                => ':attribute muss eine valide E-Mail-Adresse sein.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
+    'filled'               => ':attribute ist erforderlich.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
+    'exists'               => ':attribute ist ungültig.',
+    'image'                => ':attribute muss ein Bild sein.',
+    'image_extension'      => ':attribute muss eine gültige und unterstützte Bild-Dateiendung haben.',
+    'in'                   => ':attribute ist ungültig.',
+    'integer'              => ':attribute muss eine Zahl sein.',
+    'ip'                   => ':attribute muss eine valide IP-Adresse sein.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
+    'max'                  => [
+        'numeric' => ':attribute darf nicht größer als :max sein.',
+        'file'    => ':attribute darf nicht größer als :max Kilobyte sein.',
+        'string'  => ':attribute darf nicht länger als :max Zeichen sein.',
+        'array'   => ':attribute darf nicht mehr als :max Elemente enthalten.',
+    ],
+    'mimes'                => ':attribute muss eine Datei vom Typ: :values sein.',
+    'min'                  => [
+        'numeric' => ':attribute muss mindestens :min sein',
+        'file'    => ':attribute muss mindestens :min Kilobyte groß sein.',
+        'string'  => ':attribute muss mindestens :min Zeichen lang sein.',
+        'array'   => ':attribute muss mindesten :min Elemente enthalten.',
+    ],
+    'no_double_extension'  => ':attribute darf nur eine gültige Dateiendung',
+    'not_in'               => ':attribute ist ungültig.',
+    'not_regex'            => 'The :attribute format is invalid.',
+    'numeric'              => ':attribute muss eine Zahl sein.',
+    'regex'                => ':attribute ist in einem ungültigen Format.',
+    'required'             => ':attribute ist erforderlich.',
+    'required_if'          => ':attribute ist erforderlich, wenn :other :value ist.',
+    'required_with'        => ':attribute ist erforderlich, wenn :values vorhanden ist.',
+    'required_with_all'    => ':attribute ist erforderlich, wenn :values vorhanden sind.',
+    'required_without'     => ':attribute ist erforderlich, wenn :values nicht vorhanden ist.',
+    'required_without_all' => ':attribute ist erforderlich, wenn :values nicht vorhanden sind.',
+    'same'                 => ':attribute und :other müssen übereinstimmen.',
+    'size'                 => [
+        'numeric' => ':attribute muss :size sein.',
+        'file'    => ':attribute muss :size Kilobytes groß sein.',
+        'string'  => ':attribute muss :size Zeichen lang sein.',
+        'array'   => ':attribute muss :size Elemente enthalten.',
+    ],
+    'string'               => ':attribute muss eine Zeichenkette sein.',
+    'timezone'             => ':attribute muss eine valide zeitzone sein.',
+    'unique'               => ':attribute wird bereits verwendet.',
+    'url'                  => ':attribute ist kein valides Format.',
+    'uploaded'             => 'Die Datei konnte nicht hochgeladen werden. Der Server akzeptiert möglicherweise keine Dateien dieser Größe.',
+
+    // Custom validation lines
+    'custom' => [
+        'password-confirm' => [
+            'required_with' => 'Passwortbestätigung erforderlich',
+        ],
+    ],
+
+    // Custom validation attributes
+    'attributes' => [],
+];
index 1065945c0b0a55b89b70103fa2cfe554847aa65c..6961e049b3dc2ad15d25d2a1369f4dc6988e2a6b 100644 (file)
@@ -21,7 +21,7 @@ return [
     'email' => 'Email',
     'password' => 'Password',
     'password_confirm' => 'Confirm Password',
-    'password_hint' => 'Must be over 5 characters',
+    'password_hint' => 'Must be over 7 characters',
     'forgot_password' => 'Forgot Password?',
     'remember_me' => 'Remember Me',
     'ldap_email_hint' => 'Please enter an email to use for this account.',
@@ -64,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Please click the link in the email that was sent shortly after you registered.',
     'email_not_confirmed_resend' => 'If you cannot find the email you can re-send the confirmation email by submitting the form below.',
     'email_not_confirmed_resend_button' => 'Resend Confirmation Email',
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
 ];
\ No newline at end of file
index ed880afcf9ab24d0bdd0f30dcf6ae8f5b3de7394..1807217a375d2f0a77f5fe10ea80113ee57ea490 100644 (file)
@@ -40,6 +40,10 @@ return [
     'add' => 'Add',
 
     // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
     'sort_name' => 'Name',
     'sort_created_at' => 'Created Date',
     'sort_updated_at' => 'Updated Date',
@@ -55,8 +59,10 @@ return [
     'grid_view' => 'Grid View',
     'list_view' => 'List View',
     'default' => 'Default',
+    'breadcrumb' => 'Breadcrumb',
 
     // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => 'View Profile',
     'edit_profile' => 'Edit Profile',
 
index f6df7e71b308db293da1844e3aa8682794f08ffc..6bbc723b0abfc1e6b1f69e270bbb9d2afdbe851b 100644 (file)
@@ -176,6 +176,7 @@ return [
     'pages_delete_confirm' => 'Are you sure you want to delete this page?',
     'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?',
     'pages_editing_named' => 'Editing Page :pageName',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'Save Draft',
     'pages_edit_draft' => 'Edit Page Draft',
     'pages_editing_draft' => 'Editing Draft',
@@ -233,6 +234,7 @@ return [
     ],
     'pages_draft_discarded' => 'Draft discarded, The editor has been updated with the current page content',
     'pages_specific' => 'Specific Page',
+    'pages_is_template' => 'Page Template',
 
     // Editor Sidebar
     'page_tags' => 'Page Tags',
@@ -241,9 +243,11 @@ return [
     'shelf_tags' => 'Shelf Tags',
     'tag' => 'Tag',
     'tags' =>  'Tags',
+    'tag_name' =>  'Tag Name',
     'tag_value' => 'Tag Value (Optional)',
     'tags_explain' => "Add some tags to better categorise your content. \n You can assign a value to a tag for more in-depth organisation.",
     'tags_add' => 'Add another tag',
+    'tags_remove' => 'Remove this tag',
     'attachments' => 'Attachments',
     'attachments_explain' => 'Upload some files or attach some links to display on your page. These are visible in the page sidebar.',
     'attachments_explain_instant_save' => 'Changes here are saved instantly.',
@@ -269,6 +273,12 @@ return [
     'attachments_file_uploaded' => 'File successfully uploaded',
     'attachments_file_updated' => 'File successfully updated',
     'attachments_link_attached' => 'Link successfully attached to page',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
     // Profile View
     'profile_user_for_x' => 'User for :time',
index 40c0bbffbdce7c52ef26ef9a8efac0bd065fc06c..37de5dcc1fa5693aa3229745b13d9916099acf8a 100644 (file)
@@ -29,6 +29,7 @@ return [
     'social_account_register_instructions' => 'If you do not yet have an account, You can register an account using the :socialAccount option.',
     'social_driver_not_found' => 'Social driver not found',
     'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => 'File path :filePath could not be uploaded to. Ensure it is writable to the server.',
index 9f7d9e3cbcaccaaa5631115f4d387785b196c18c..f41ca7868f66f2937e8ad49e7e2daee1eff3c569 100644 (file)
@@ -6,7 +6,7 @@
  */
 return [
 
-    'password' => 'Passwords must be at least six characters and match the confirmation.',
+    'password' => 'Passwords must be at least eight characters and match the confirmation.',
     'user' => "We can't find a user with that e-mail address.",
     'token' => 'This password reset token is invalid.',
     'sent' => 'We have e-mailed your password reset link!',
index d275e330a480b00bfde576d31bf28ed9fd7dcb57..56e0868e4b80dc4830a8966f7122a0c4ae969357 100755 (executable)
@@ -63,6 +63,13 @@ return [
     'maint_image_cleanup_warning' => ':count potentially unused images were found. Are you sure you want to delete these images?',
     'maint_image_cleanup_success' => ':count potentially unused images found and deleted!',
     'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!',
+    'maint_send_test_email' => 'Send a Test Email',
+    'maint_send_test_email_desc' => 'This sends a test email to your email address specified in your profile.',
+    'maint_send_test_email_run' => 'Send test email',
+    'maint_send_test_email_success' => 'Email sent to :address',
+    'maint_send_test_email_mail_subject' => 'Test Email',
+    'maint_send_test_email_mail_greeting' => 'Email delivery seems to work!',
+    'maint_send_test_email_mail_text' => 'Congratulations! As you received this email notification, your email settings seem to be configured properly.',
 
     // Role Settings
     'roles' => 'Roles',
@@ -85,6 +92,7 @@ return [
     'role_manage_roles' => 'Manage roles & role permissions',
     'role_manage_entity_permissions' => 'Manage all book, chapter & page permissions',
     'role_manage_own_entity_permissions' => 'Manage permissions on own book, chapter & pages',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'Manage app settings',
     'role_asset' => 'Asset Permissions',
     'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
@@ -108,7 +116,9 @@ return [
     'users_role' => 'User Roles',
     'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
     'users_password' => 'User Password',
-    'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 5 characters long.',
+    'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 6 characters long.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => 'External Authentication ID',
     'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your LDAP system.',
     'users_password_warning' => 'Only fill the below if you would like to change your password.',
@@ -132,9 +142,8 @@ return [
     'users_social_connected' => ':socialAccount account was successfully attached to your profile.',
     'users_social_disconnected' => ':socialAccount account was successfully disconnected from your profile.',
 
-    //! Since these labels are already localized this array does not need to be
-    //! translated in the language-specific files.
-    //! DELETE BELOW IF COPIED FROM EN
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
     //!////////////////////////////////
     'language_select' => [
         'en' => 'English',
@@ -149,15 +158,16 @@ return [
         'sk' => 'Slovensky',
         'cs' => 'Česky',
         'sv' => 'Svenska',
-        'kr' => '한국어',
+        'ko' => '한국어',
         'ja' => '日本語',
         'pl' => 'Polski',
         'it' => 'Italian',
         'ru' => 'Русский',
         'uk' => 'Українська',
         'zh_CN' => '简体中文',
-       'zh_TW' => '繁體中文',
-       'hu' => 'Magyar'
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
     ]
     //!////////////////////////////////
 ];
index 210980ac2e172244dbcb9a52016afff7fcb5ef8d..76b57a2a3b58ddb8ef41e0562c5187359cc6e542 100644 (file)
@@ -12,7 +12,7 @@ return [
     'active_url'           => 'The :attribute is not a valid URL.',
     'after'                => 'The :attribute must be a date after :date.',
     'alpha'                => 'The :attribute may only contain letters.',
-    'alpha_dash'           => 'The :attribute may only contain letters, numbers, and dashes.',
+    'alpha_dash'           => 'The :attribute may only contain letters, numbers, dashes and underscores.',
     'alpha_num'            => 'The :attribute may only contain letters and numbers.',
     'array'                => 'The :attribute must be an array.',
     'before'               => 'The :attribute must be a date before :date.',
@@ -30,13 +30,41 @@ return [
     'digits'               => 'The :attribute must be :digits digits.',
     'digits_between'       => 'The :attribute must be between :min and :max digits.',
     'email'                => 'The :attribute must be a valid email address.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => 'The :attribute field is required.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => 'The selected :attribute is invalid.',
     'image'                => 'The :attribute must be an image.',
     'image_extension'      => 'The :attribute must have a valid & supported image extension.',
     'in'                   => 'The selected :attribute is invalid.',
     'integer'              => 'The :attribute must be an integer.',
     'ip'                   => 'The :attribute must be a valid IP address.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => 'The :attribute may not be greater than :max.',
         'file'    => 'The :attribute may not be greater than :max kilobytes.',
@@ -52,6 +80,7 @@ return [
     ],
     'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => 'The selected :attribute is invalid.',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => 'The :attribute must be a number.',
     'regex'                => 'The :attribute format is invalid.',
     'required'             => 'The :attribute field is required.',
index c93751a10ab7bd9749606cfc82e40181a0b91e0a..3636507fee72669b19fcc12aa023e1eaad476e22 100644 (file)
@@ -21,7 +21,7 @@ return [
     'email' => 'Correo electrónico',
     'password' => 'Contraseña',
     'password_confirm' => 'Confirmar Contraseña',
-    'password_hint' => 'Debe contener más de 5 caracteres',
+    'password_hint' => 'Debe contener más de 7 caracteres',
     'forgot_password' => '¿Contraseña Olvidada?',
     'remember_me' => 'Recordarme',
     'ldap_email_hint' => 'Por favor introduzca un mail para utilizar con esta cuenta.',
@@ -64,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Por favor siga el enlace en el correo electrónico que ha sido enviado durante el proceso de registro.',
     'email_not_confirmed_resend' => 'Si no puede encontrar el correo electrónico, puede solicitar el renvío del correo electrónico de confirmación rellenando el formulario que se muestra a continuación.',
     'email_not_confirmed_resend_button' => 'Reenviar Correo Electrónico de confirmación',
+
+    // User Invite
+    'user_invite_email_subject' => 'As sido invitado a unirte a :appName!',
+    'user_invite_email_greeting' => 'Se ha creado una cuenta para usted en :appName.',
+    'user_invite_email_text' => 'Clica en el botón a continuación para ajustar una contraseña y poder acceder:',
+    'user_invite_email_action' => 'Ajustar la Contraseña de la Cuenta',
+    'user_invite_page_welcome' => '¡Bienvenido a :appName!',
+    'user_invite_page_text' => 'Para completar la cuenta y tener acceso es necesario que configure una contraseña que se utilizará para entrar en :appName en futuros accesos.',
+    'user_invite_page_confirm_button' => 'Confirmar Contraseña',
+    'user_invite_success' => '¡Contraseña guardada, ya tiene acceso a :appName!'
 ];
\ No newline at end of file
index 594a8cf00bbdb2a6fdc65ad9f0ee1457b4188651..a1c16cf95377c1b38f216fb38a9d970e00ec1517 100644 (file)
@@ -20,7 +20,7 @@ return [
     'role' => 'Rol',
     'cover_image' => 'Imagen de portada',
     'cover_image_description' => 'Esta imagen debe ser aproximadamente de 440x250px.',
-
+    
     // Actions
     'actions' => 'Acciones',
     'view' => 'Ver',
@@ -40,6 +40,10 @@ return [
     'add' => 'Añadir',
 
     // Sort Options
+    'sort_options' => 'Opciones de ordenación',
+    'sort_direction_toggle' => 'Cambiar el Orden',
+    'sort_ascending' => 'Ordenar Ascendentemente',
+    'sort_descending' => 'Ordenar Descendentemente',
     'sort_name' => 'Nombre',
     'sort_created_at' => 'Fecha de Creación',
     'sort_updated_at' => 'Fecha de Modificación',
@@ -55,8 +59,10 @@ return [
     'grid_view' => 'Vista en Cuadrícula',
     'list_view' => 'Vista en Lista',
     'default' => 'Predeterminada',
+    'breadcrumb' => 'Rastro de migas de pan',
 
     // Header
+    'profile_menu' => 'Menú de Perfil',
     'view_profile' => 'Ver Perfil',
     'edit_profile' => 'Editar Perfil',
 
index 09b318eaa1cf591c62e623981f394165be47147a..db87efda7eeb7655ea90f9ad4d05f12fe784074a 100644 (file)
@@ -176,7 +176,7 @@ return [
     'pages_delete_confirm' => '¿Está seguro de borrar esta página?',
     'pages_delete_draft_confirm' => '¿Está seguro de que desea borrar este borrador de página?',
     'pages_editing_named' => 'Editando página :pageName',
-    'pages_edit_toggle_header' => 'Toggle Título',
+    'pages_edit_draft_options' => 'Opciones de Borrador',
     'pages_edit_save_draft' => 'Guardar borrador',
     'pages_edit_draft' => 'Editar borrador de página',
     'pages_editing_draft' => 'Editando borrador',
@@ -234,6 +234,7 @@ return [
     ],
     'pages_draft_discarded' => 'Borrador descartado, el editor ha sido actualizado con el contenido de la página actual',
     'pages_specific' => 'Página específica',
+    'pages_is_template' => 'Página es plantilla',
 
     // Editor Sidebar
     'page_tags' => 'Etiquetas de Página',
@@ -242,9 +243,11 @@ return [
     'shelf_tags' => 'Etiquetas de Estante',
     'tag' => 'Etiqueta',
     'tags' =>  'Etiquetas',
+    'tag_name' =>  'Nombre de la Etiqueta',
     'tag_value' => 'Valor de la etiqueta (Opcional)',
     'tags_explain' => "Agrege algunas etiquetas para mejorar la categorización de su contenido. \n Puede asignar un valor a una etiqueta para una organización a mayor detalle.",
     'tags_add' => 'Agregar otra etiqueta',
+    'tags_remove' => 'Eliminar esta etiqueta',
     'attachments' => 'Adjuntos',
     'attachments_explain' => 'Subir ficheros o agregar enlaces para mostrar en la página. Estos son visibles en la barra lateral de la página.',
     'attachments_explain_instant_save' => 'Los cambios son guardados de manera instantánea .',
@@ -269,7 +272,13 @@ return [
     'attachments_deleted' => 'Adjunto borrado',
     'attachments_file_uploaded' => 'Fichero subido éxitosamente',
     'attachments_file_updated' => 'Fichero actualizado éxitosamente',
-    'attachments_link_attached' => 'Enlace agregado éxitosamente a la ágina',
+    'attachments_link_attached' => 'Enlace agregado éxitosamente a la página',
+    'templates' => 'Plantillas',
+    'templates_set_as_template' => 'La página es una plantilla',
+    'templates_explain_set_as_template' => 'Puede ajustar esta página como una plantilla, así su contenido puede emplearse al crear una nueva página. Otros usuarios podrán utilizar esta plantilla si tienen permisos de lectura sobre esta página.',
+    'templates_replace_content' => 'Reemplazar el contenido de la página',
+    'templates_append_content' => 'Añadir después del contenido de la página',
+    'templates_prepend_content' => 'Añadir antes del contenido de la página',
 
     // Profile View
     'profile_user_for_x' => 'Usuario para :time',
index 51834f18dfa2abf02290ca037abaf20e531574a4..77d5520f4868208e282bb70cbe2a93e83c513b9e 100644 (file)
@@ -27,6 +27,7 @@ return [
     'social_account_register_instructions' => 'Si no dispone de una cuenta, puede registrar una cuenta usando la opción de :socialAccount .',
     'social_driver_not_found' => 'Driver social no encontrado',
     'social_driver_not_configured' => 'Su configuración :socialAccount no es correcta.',
+    'invite_token_expired' => 'Este enlace de invitación ha expirado. Puede resetear la contraseña de su cuenta como alternativa.',
 
     // System
     'path_not_writable' => 'El fichero no pudo ser subido a la ruta :filePath . Asegúrese de que es escribible por el servidor.',
index 83fe73562a2fd9f41cffed4a66672dad0f06a3f5..f448bdd22e9834e626d73c49682012e6df2d416d 100644 (file)
@@ -29,6 +29,7 @@ return [
     'app_editor_desc' => 'Seleccione qué editor se usará por todos los usuarios para editar páginas.',
     'app_custom_html' => 'Contenido de cabecera HTML personalizado',
     'app_custom_html_desc' => 'Cualquier contenido agregado aquí será insertado al final de la sección <head> de cada página. Esto es útil para sobreescribir estilos o agregar código para analíticas web.',
+    'app_custom_html_disabled_notice' => 'El contenido personalizado para la cabecera está deshabilitado en esta página de ajustes para permitir que cualquier cambio que rompa la funcionalidad pueda ser revertido.',
     'app_logo' => 'Logo de la Aplicación',
     'app_logo_desc' => 'Esta imagen debería de ser 43px de altura. <br> Las imágenes grandes serán escaladas.',
     'app_primary_color' => 'Color Primario de la Aplicación',
@@ -84,6 +85,7 @@ return [
     'role_manage_roles' => 'Gestionar roles y permisos de roles',
     'role_manage_entity_permissions' => 'Gestionar todos los permisos de libros, capítulos y páginas',
     'role_manage_own_entity_permissions' => 'Gestionar permisos en libros, capítulos y páginas propias',
+    'role_manage_page_templates' => 'Administrar plantillas',
     'role_manage_settings' => 'Gestionar ajustes de la aplicación',
     'role_asset' => 'Permisos de contenido',
     'role_asset_desc' => 'Estos permisos controlan el acceso por defecto a los contenidos del sistema. Los permisos de Libros, Capítulos y Páginas sobreescribiran estos permisos.',
@@ -100,14 +102,16 @@ return [
     'users' => 'Usuarios',
     'user_profile' => 'Perfil de Usuario',
     'users_add_new' => 'Agregar Nuevo Usuario',
+    'users_search' => 'Buscar usuarios',
     'users_details' => 'Detalles de Usuario',
     'users_details_desc' => 'Ajusta un nombre público y email para este usuario. El email será empleado para acceder a la aplicación.',
     'users_details_desc_no_email' => 'Ajusta un nombre público para este usuario para que pueda ser reconocido por otros.',
-    'users_search' => 'Buscar usuarios',
     'users_role' => 'Roles de usuario',
     'users_role_desc' => 'Selecciona los roles a los que será asignado este usuario. Si se asignan varios roles los permisos se acumularán y recibirá todas las habilidades de los roles asignados.',
     'users_password' => 'Contraseña de Usuario',
     'users_password_desc' => 'Ajusta una contraseña que se utilizará para acceder a la aplicación. Debe ser al menos de 5 caracteres de longitud.',
+    'users_send_invite_text' => 'Puede enviar una invitación a este usuario por correo electrónico que le permitirá ajustar su propia contraseña, o puede usted ajustar su contraseña.',
+    'users_send_invite_option' => 'Enviar un correo electrónico de invitación',
     'users_external_auth_id' => 'ID externo de autenticación',
     'users_external_auth_id_desc' => 'Esta es la ID usada para asociar este usuario con LDAP.',
     'users_password_warning' => 'Solo debe rellenar este campo si desea cambiar su contraseña.',
@@ -131,4 +135,32 @@ return [
     'users_social_connected' => 'La cuenta :socialAccount ha sido añadida éxitosamente a su perfil.',
     'users_social_disconnected' => 'La cuenta :socialAccount ha sido desconectada éxitosamente de su perfil.',
 
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index 26f713ebdd5f6bcb8c4e1300fec9bb7e5a51f3d7..c30e168fc03b2c8a3a64b2dcffc626f5661b48c3 100644 (file)
@@ -30,13 +30,41 @@ return [
     'digits'               => ':attribute debe ser de :digits dígitos.',
     'digits_between'       => ':attribute debe ser un valor entre :min y :max dígios.',
     'email'                => ':attribute debe ser un correo electrónico válido.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => 'El campo :attribute es requerido.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => 'El :attribute seleccionado es inválido.',
     'image'                => 'El :attribute debe ser una imagen.',
     'image_extension'      => 'El :attribute debe tener una extensión de imagen válida y soportada.',
     'in'                   => 'El selected :attribute es inválio.',
     'integer'              => 'El :attribute debe ser un entero.',
     'ip'                   => 'El :attribute debe ser una dirección IP válida.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => 'El :attribute no puede ser mayor que :max.',
         'file'    => 'El :attribute no puede ser mayor que :max kilobytes.',
@@ -52,6 +80,7 @@ return [
     ],
     'no_double_extension'  => 'El :attribute solo debe tener una extensión de archivo.',
     'not_in'               => 'El :attribute seleccionado es inválio.',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => 'El :attribute debe ser numérico.',
     'regex'                => 'El formato de :attribute es inválido',
     'required'             => 'El :attribute es requerido.',
index 50d178202244fb7c17a299cd24b99e9fed1c3657..f8f9e84367568979b8faaca2fda33af9b7d6ebb9 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => 'página creada',
     'page_create_notification'    => 'Página creada exitosamente',
index 4899b3d8998b39855552c8d21a6ec3d9b0743347..aa54fa71f6541352370f9876027eb91fc5cd719c 100644 (file)
@@ -1,37 +1,33 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => 'Las credenciales no concuerdan con nuestros registros.',
     'throttle' => 'Demasiados intentos fallidos de conexión. Por favor intente nuevamente en :seconds segundos.',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => 'Registrarse',
     'log_in' => 'Acceder',
     'log_in_with' => 'Acceder con :socialDriver',
     'sign_up_with' => 'Registrarse con :socialDriver',
-    'logout' => 'Logout',
+    'logout' => 'Salir',
 
     'name' => 'Nombre',
     'username' => 'Nombre de usuario',
     'email' => 'Correo electrónico',
     'password' => 'Contraseña',
     'password_confirm' => 'Confirmar contraseña',
-    'password_hint' => 'Debe contener al menos 5 caracteres',
+    'password_hint' => 'Debe contener al menos 7 caracteres',
     'forgot_password' => '¿Olvidó la contraseña?',
     'remember_me' => 'Recordarme',
     'ldap_email_hint' => 'Por favor introduzca un correo electrónico para utilizar con esta cuenta.',
     'create_account' => 'Crear una cuenta',
+    'already_have_account' => '¿Ya tiene una cuenta?',
+    'dont_have_account' => '¿No tiene una cuenta?',
     'social_login' => 'Acceso con cuenta Social',
     'social_registration' => 'Registro con cuenta Social',
     'social_registration_text' => 'Registrar y entrar utilizando otro servicio.',
@@ -43,23 +39,18 @@ return [
     'register_success' => '¡Gracias por registrarse! Ahora se encuentra registrado y ha accedido a la aplicación.',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => 'Restablecer la contraseña',
     'reset_password_send_instructions' => 'Introduzca su correo electrónico a continuación y se le enviará un correo electrónico con un enlace para la restauración',
     'reset_password_send_button' => 'Enviar enlace de restauración',
     'reset_password_sent_success' => 'Se envió un enlace para restablecer la contraseña a :email.',
     'reset_password_success' => 'Su contraseña se restableció con éxito.',
-
     'email_reset_subject' => 'Restauración de la contraseña de para la aplicación :appName',
     'email_reset_text' => 'Ud. esta recibiendo este correo electrónico debido a que recibimos una solicitud de restauración de la contraseña de su cuenta.',
     'email_reset_not_requested' => 'Si ud. no solicitó un cambio de contraseña, no se requiere ninguna acción.',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => 'Confirme su correo electrónico en :appName',
     'email_confirm_greeting' => '¡Gracias por unirse a :appName!',
     'email_confirm_text' => 'Por favor confirme su dirección de correo electrónico presionando en el siguiente botón:',
@@ -73,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Por favor verifique el correo electrónico con el enlace de confirmación que fue enviado luego de registrarse.',
     'email_not_confirmed_resend' => 'Si no puede encontrar el correo electrónico, puede solicitar el renvío del correo electrónico de confirmación rellenando el formulario a continuación.',
     'email_not_confirmed_resend_button' => 'Reenviar correo electrónico de confirmación',
-];
+
+    // User Invite
+    'user_invite_email_subject' => 'Lo invitaron a unirse a :appName!',
+    'user_invite_email_greeting' => 'Se creó una cuenta para usted en :appName.',
+    'user_invite_email_text' => 'Presione el botón de abajo para establecer una contraseña y tener acceso access:',
+    'user_invite_email_action' => 'Establecer la contraseña de la cuenta',
+    'user_invite_page_welcome' => 'Bienvenido a :appName!',
+    'user_invite_page_text' => 'Para finalizar la cuenta y tener acceso debe establcer una contraseña que utilizará para ingresar a :appName en visitas futuras.',
+    'user_invite_page_confirm_button' => 'Confirmar Contraseña',
+    'user_invite_success' => 'Contraseña establecida, ahora tiene acceso a :appName!'
+];
\ No newline at end of file
index eb74315355ef5d83221544d3c3b3f8447c74663b..3d45ce6cb514d8f46845cc73cefec760fba56010 100644 (file)
@@ -1,31 +1,30 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => 'Cancelar',
     'confirm' => 'Confirmar',
     'back' => 'Atrás',
     'save' => 'Guardar',
     'continue' => 'Continuar',
     'select' => 'Seleccionar',
+    'toggle_all' => 'Alternar todo',
     'more' => 'Más',
-    
-    /**
-     * Form Labels
-     */
+
+    // Form Labels
     'name' => 'Nombre',
     'description' => 'Descripción',
     'role' => 'Rol',
     'cover_image' => 'Imagen de cubierta',
     'cover_image_description' => 'Esta imagen debe ser de 440x250px aproximadamente.',
-
-    /**
-     * Actions
-     */
+    
+    // Actions
     'actions' => 'Acciones',
     'view' => 'Ver',
+    'view_all' => 'Ver todo',
     'create' => 'Crear',
     'update' => 'Actualizar',
     'edit' => 'Editar',
@@ -40,29 +39,38 @@ return [
     'remove' => 'Remover',
     'add' => 'Agregar',
 
-    /**
-     * Misc
-     */
+    // Sort Options
+    'sort_options' => 'Opciones de Orden',
+    'sort_direction_toggle' => 'Cambiar Dirección de Orden',
+    'sort_ascending' => 'Orden Ascendente',
+    'sort_descending' => 'Orden Descendente',
+    'sort_name' => 'Nombre',
+    'sort_created_at' => 'Fecha de creación',
+    'sort_updated_at' => 'Fecha de actualización',
+
+    // Misc
     'deleted_user' => 'Usuario borrado',
     'no_activity' => 'Ninguna actividad para mostrar',
     'no_items' => 'No hay items disponibles',
     'back_to_top' => 'Volver arriba',
     'toggle_details' => 'Alternar detalles',
-    "toggle_thumbnails" => "Alternar miniaturas",
+    'toggle_thumbnails' => 'Alternar miniaturas',
     'details' => 'Detalles',
-    "grid_view" => "Vista de grilla",
-    "list_view" => "Vista de lista",
+    'grid_view' => 'Vista de grilla',
+    'list_view' => 'Vista de lista',
     'default' => 'Por defecto',
+    'breadcrumb' => 'Miga de Pan',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Menu del Perfil',
     'view_profile' => 'Ver Perfil',
     'edit_profile' => 'Editar Perfil',
 
-    /**
-     * Email Content
-     */
+    // Layout tabs
+    'tab_info' => 'Información',
+    'tab_content' => 'Contenido',
+
+    // Email Content
     'email_action_help' => 'Si está teniendo problemas haga click en el botón ":actionText", copie y pegue la siguiente URL en su navegador web:',
     'email_rights' => 'Todos los derechos reservados',
 ];
index ea61f5f4ca0f4e3e98b085b3990651c5a99746d2..d205afbc115d94ea6ebe03b1415cb52f841b5113 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => 'Seleccionar Imagen',
     'image_all' => 'Todo',
     'image_all_title' => 'Ver todas las imágenes',
@@ -24,9 +25,7 @@ return [
     'image_delete_success' => 'Imagen borrada exitosamente',
     'image_upload_remove' => 'Quitar',
 
-    /**
-     * Code editor
-     */
+    // Code Editor
     'code_editor' => 'Editar Código',
     'code_language' => 'Lenguaje del Código',
     'code_content' => 'Contenido del Código',
index e8d01743be6033b1aa701664b75dc6a08a698bc7..700e873c31d1d0c5786c14867b7b5370116bdc5f 100644 (file)
@@ -1,16 +1,19 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
-    'recently_created' => 'Recientemente creado',
-    'recently_created_pages' => 'Páginas recientemente creadas',
-    'recently_updated_pages' => 'Páginas recientemente actualizadas',
-    'recently_created_chapters' => 'Capítulos recientemente creados',
-    'recently_created_books' => 'Libros recientemente creados',
-    'recently_update' => 'Recientemente actualizado',
-    'recently_viewed' => 'Recientemente visto',
+    // Shared
+    'recently_created' => 'Creado recientemente',
+    'recently_created_pages' => 'Páginas creadas recientemente',
+    'recently_updated_pages' => 'Páginas actualizadas recientemente',
+    'recently_created_chapters' => 'Capítulos creados recientemente',
+    'recently_created_books' => 'Libros creados recientemente',
+    'recently_created_shelves' => 'Estantes creados recientemente',
+    'recently_update' => 'Actaulizado recientemente',
+    'recently_viewed' => 'Visto recientemente',
     'recent_activity' => 'Actividad reciente',
     'create_now' => 'Crear uno ahora',
     'revisions' => 'Revisiones',
@@ -31,17 +34,13 @@ return [
     'export_pdf' => 'Archivo PDF',
     'export_text' => 'Archivo de texto plano',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => 'Permisos',
     'permissions_intro' => 'una vez habilitado, Estos permisos tendrán prioridad por encima de cualquier permiso establecido.',
     'permissions_enable' => 'Habilitar permisos custom',
     'permissions_save' => 'Guardar permisos',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => 'Buscar resultados',
     'search_total_results_found' => ':count resultados encontrados|:count total de resultados encontrados',
     'search_clear' => 'Limpiar resultados',
@@ -66,16 +65,16 @@ return [
     'search_set_date' => 'Esablecer fecha',
     'search_update' => 'Actualizar búsqueda',
 
-    /**
-     * Shelves
-     */
+    // Shelves
     'shelf' => 'Estante',
     'shelves' => 'Estantes',
+    'x_shelves' => ':count Estante|:count Estantes',
     'shelves_long' => 'Estantes de libros',
     'shelves_empty' => 'No se crearon estantes',
     'shelves_create' => 'Crear un estante nuevo',
     'shelves_popular' => 'Estantes Populares',
     'shelves_new' => 'Estantes Nuevos',
+    'shelves_new_action' => 'Estante Nuevo',
     'shelves_popular_empty' => 'Los estantes más populares aparecerán aquí.',
     'shelves_new_empty' => 'Los estantes mas nuevos aparecerán aquí.',
     'shelves_save' => 'Guardar estantes',
@@ -98,9 +97,7 @@ return [
     'shelves_copy_permissions_explain' => 'Esta acción aplicará los permisos de este estante a todos los libros contenidos en él. Antes de activarlos, asegúrese que los cambios a los permisos de este estante estén guardados.',
     'shelves_copy_permission_success' => 'Se copiaron los permisos del estante a :count libros',
 
-    /**
-     * Books
-     */
+    // Books
     'book' => 'Libro',
     'books' => 'Libros',
     'x_books' => ':count Libro|:count Libros',
@@ -108,6 +105,7 @@ return [
     'books_popular' => 'Libros populares',
     'books_recent' => 'Libros recientes',
     'books_new' => 'Libros nuevos',
+    'books_new_action' => 'Libro nuevo',
     'books_popular_empty' => 'Los libros más populares aparecerán aquí.',
     'books_new_empty' => 'Los libros creados más recientemente aparecerán aquí.',
     'books_create' => 'Crear nuevo libro',
@@ -123,7 +121,6 @@ return [
     'books_permissions_updated' => 'Permisos de libro actualizados',
     'books_empty_contents' => 'Ninguna página o capítulo ha sido creada para este libro.',
     'books_empty_create_page' => 'Crear una nueva página',
-    'books_empty_or' => 'ó',
     'books_empty_sort_current_book' => 'Organizar el libro actual',
     'books_empty_add_chapter' => 'Agregar un capítulo',
     'books_permissions_active' => 'Permisos de libro activados',
@@ -131,12 +128,15 @@ return [
     'books_navigation' => 'Navegación de libro',
     'books_sort' => 'Organizar contenido de libro',
     'books_sort_named' => 'Organizar libro :bookName',
+    'books_sort_name' => 'Organizar por nombre',
+    'books_sort_created' => 'Organizar por fecha de creación',
+    'books_sort_updated' => 'Organizar por fecha de actualización',
+    'books_sort_chapters_first' => 'Capítulos primero',
+    'books_sort_chapters_last' => 'Capítulos al final',
     'books_sort_show_other' => 'Mostrar otros libros',
     'books_sort_save' => 'Guardar nuevo orden',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => 'Capítulo',
     'chapters' => 'Capítulos',
     'x_chapters' => ':count Capítulo|:count Capítulos',
@@ -159,9 +159,7 @@ return [
     'chapters_permissions_success' => 'Permisos de capítulo actualizados',
     'chapters_search_this' => 'Buscar en este capítulo',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => 'Página',
     'pages' => 'Páginas',
     'x_pages' => ':count Página|:count Páginas',
@@ -178,7 +176,7 @@ return [
     'pages_delete_confirm' => '¿Está seguro de borrar esta página?',
     'pages_delete_draft_confirm' => 'Está seguro de que desea borrar este borrador de página?',
     'pages_editing_named' => 'Editando página :pageName',
-    'pages_edit_toggle_header' => 'Alternar cabecera',
+    'pages_edit_draft_options' => 'Opciones de borrador',
     'pages_edit_save_draft' => 'Guardar borrador',
     'pages_edit_draft' => 'Editar borrador de página',
     'pages_editing_draft' => 'Editando borrador',
@@ -212,6 +210,8 @@ return [
     'pages_revisions_created_by' => 'Creado por',
     'pages_revisions_date' => 'Fecha de revisión',
     'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revisión #:id',
+    'pages_revisions_numbered_changes' => 'Cambios de Revisión #:id',
     'pages_revisions_changelog' => 'Registro de cambios',
     'pages_revisions_changes' => 'Cambios',
     'pages_revisions_current' => 'Versión actual',
@@ -234,20 +234,20 @@ return [
     ],
     'pages_draft_discarded' => 'Borrador descartado, el editor ha sido actualizado con el contenido de la página actual',
     'pages_specific' => 'Página Específica',
+    'pages_is_template' => 'Plantilla de Página',
 
-
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => 'Etiquetas de página',
     'chapter_tags' => 'Etiquetas de capítulo',
     'book_tags' => 'Etiquetas de libro',
-    'shelf_tags' => 'Shelf Tags',
+    'shelf_tags' => 'Etiquetas de Estante',
     'tag' => 'Etiqueta',
     'tags' =>  'Etiquetas',
+    'tag_name' =>  'Nombre de etiqueta',
     'tag_value' => 'Valor de la etiqueta (Opcional)',
     'tags_explain' => "Agregar algunas etiquetas para mejorar la categorización de su contenido. \n Se puede asignar un valor a una etiqueta para una organizacón con mayor detalle.",
     'tags_add' => 'Agregar otra etiqueta',
+    'tags_remove' => 'Eliminar esta etiqueta',
     'attachments' => 'Adjuntos',
     'attachments_explain' => 'Subir archivos o agregar enlaces para mostrar en la página. Estos son visibles en la barra lateral de la página.',
     'attachments_explain_instant_save' => 'Los cambios se guardan de manera instantánea.',
@@ -273,19 +273,22 @@ return [
     'attachments_file_uploaded' => 'Archivo subido exitosamente',
     'attachments_file_updated' => 'Archivo actualizado exitosamente',
     'attachments_link_attached' => 'Enlace agregado exitosamente a la página',
+    'templates' => 'Plantillas',
+    'templates_set_as_template' => 'La Página es una plantilla',
+    'templates_explain_set_as_template' => 'Puede establecer esta página como plantilla para que el contenido pueda utilizarse para al crear otras páginas. Otris usuarios podrán utilizar esta plantilla si tienen permisos para ver de esta página.',
+    'templates_replace_content' => 'Reemplazar el contenido de la página',
+    'templates_append_content' => 'Incorporar al fina del contenido de la página',
+    'templates_prepend_content' => 'Incorporar al principio del contenido de la página',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => 'Usuario para :time',
     'profile_created_content' => 'Contenido creado',
-    'profile_not_created_pages' => ':userName no ha creado ninguna página',
-    'profile_not_created_chapters' => ':userName no ha creado ningún capítulo',
-    'profile_not_created_books' => ':userName no ha creado ningún libro',
+    'profile_not_created_pages' => ':userName no ha creado páginas',
+    'profile_not_created_chapters' => ':userName no ha creado capítulos',
+    'profile_not_created_books' => ':userName no ha creado libros',
+    'profile_not_created_shelves' => ':userName no ha creado estantes',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => 'Comentario',
     'comments' => 'Comentarios',
     'comment_add' => 'Agregar comentario',
@@ -303,10 +306,9 @@ return [
     'comment_delete_confirm' => '¿Está seguro que quiere borrar este comentario?',
     'comment_in_reply_to' => 'En respuesta a :commentId',
 
-     /**
-     * Revision
-     */
-    'revision_delete_confirm' => 'Are you sure you want to delete this revision?',
+    // Revision
+    'revision_delete_confirm' => '¿Está seguro de que quiere eliminar esta revisión?',
+    'revision_restore_confirm' => '¿Está seguro de que quiere restaurar esta revisión? Se reemplazará el contenido de la página actual.',
     'revision_delete_success' => 'Revisión eliminada',
     'revision_cannot_delete_latest' => 'No se puede eliminar la última revisión.'
-];
+];
\ No newline at end of file
index d0caf942033e844053d4e8c39b1ed9b8d564d3bd..fb3c5e9138daab4e6141ec0ff7bfb0b65dc3f082 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => 'Ud. no tiene permisos para visualizar la página solicitada.',
     'permissionJson' => 'Ud. no tiene permisos para ejecutar la acción solicitada.',
@@ -29,13 +27,14 @@ return [
     'social_account_register_instructions' => 'Si no dispone de una cuenta, puede registrar una cuenta usando la opción de :socialAccount .',
     'social_driver_not_found' => 'Driver social no encontrado',
     'social_driver_not_configured' => 'Su configuración :socialAccount no es correcta.',
+    'invite_token_expired' => 'El enace de la esta invitación expiró. Puede intentar restablecer la contraseña de su cuenta',
 
     // System
     'path_not_writable' => 'La ruta :filePath no pudo ser cargada. Asegurese de que es escribible por el servidor.',
     'cannot_get_image_from_url' => 'No se puede obtener la imagen desde :url',
     'cannot_create_thumbs' => 'El servidor no puede crear la imagen miniatura. Por favor chequee que tiene la extensión GD instalada.',
     'server_upload_limit' => 'El servidor no permite la subida de ficheros de este tamañ. Por favor intente con un fichero de menor tamañ.',
-    'uploaded'  => 'El servidor no permite subir archivos de este tamaño. Por favor intente un tamaño menor.',    'image_upload_error' => 'Ha ocurrido un error al subir la imagen',
+    'uploaded'  => 'El servidor no permite subir archivos de este tamaño. Por favor intente un tamaño menor.',
     'image_upload_error' => 'Ha ocurrido un error al subir la imagen',
     'image_upload_type_error' => 'El tipo de imagen subida es inválido.',
     'file_upload_timeout' => 'La carga del archivo ha caducado.',
@@ -82,4 +81,5 @@ return [
     'error_occurred' => 'Ha ocurrido un error',
     'app_down' => 'La aplicación :appName se encuentra caída en este momento',
     'back_soon' => 'Volverá a estar operativa en corto tiempo.',
+
 ];
index 325916dc39b8c3ca3abbcabdf616d96243d8fc62..df81d0aa87e712e4999522549779f916eb75c8ac 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; Anterior',
     'next'     => 'Siguiente &raquo;',
 
index 62ca0e16eca831fc20fe913ee87eb334cfb1e461..e38e55ed80e619d998d1a8fc353614e272551be2 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => 'La contraseña debe ser como mínimo de seis caracteres y coincidir con la confirmación.',
     'user' => "No podemos encontrar un usuario con esta dirección de correo electrónico.",
     'token' => 'Este token de restablecimiento de contraseña no es válido.',
index 0b32b601155ae925a66f6f1f5a0fa124a8b2ab1f..e9aad712896cb43d303c8f2525dab00d4dc9fbb8 100644 (file)
@@ -1,32 +1,35 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
-
+    // Common Messages
     'settings' => 'Ajustes',
     'settings_save' => 'Guardar ajustes',
     'settings_save_success' => 'Ajustes guardados',
 
-    /**
-     * App settings
-     */
-
-    'app_settings' => 'Ajustes de Aplicación',
+    // App Settings
+    'app_customization' => 'Personalización',
+    'app_features_security' => 'Características y Seguridad',
     'app_name' => 'Nombre de aplicación',
     'app_name_desc' => 'Este nombre se muestra en la cabecera y en cualquier correo electrónico de la aplicación',
     'app_name_header' => '¿Mostrar el nombre de la aplicación en la cabecera?',
+    'app_public_access' => 'Acceso Público',
+    'app_public_access_desc' => 'Habilitar esta opción permitirá a los visitantes, que no estén autenticados, acceder al contenido en la instancia de BookStack.',
+    'app_public_access_desc_guest' => 'El acceso de visitantes públicos se puede controlar mediante el usuario "Guest/Invitado".',
+    'app_public_access_toggle' => 'Permitir el acceso público',
     'app_public_viewing' => '¿Permitir vista pública?',
     'app_secure_images' => '¿Habilitar mayor seguridad para subir imágenes?',
+    'app_secure_images_toggle' => 'Habilitar seguridad alta para subir imágenes',
     'app_secure_images_desc' => 'Por razones de rendimiento, todas las imágenes son públicas. Esta opción agrega una cadena larga difícil de adivinar, asegúrese que los índices de directorios no están habilitados para prevenir el acceso fácil a las imágenes.',
     'app_editor' => 'Editor de página',
     'app_editor_desc' => 'Seleccione cuál editor será usado por todos los usuarios para editar páginas.',
     'app_custom_html' => 'Contenido de cabecera HTML personalizable',
     'app_custom_html_desc' => 'Cualquier contenido agregado aquí será agregado al final de la sección <head> de cada página. Esto es útil para sobreescribir estilos o agregar código para analíticas.',
+    'app_custom_html_disabled_notice' => 'El contenido personailzado para la cabecera HTML está deshabilitado en esta configuración para garantizar que cualquier cambio importante se pueda revertir.',
     'app_logo' => 'Logo de la aplicación',
     'app_logo_desc' => 'Esta imagen debería ser de 43px en altura. <br>Las imágenes grandes seán escaladas.',
     'app_primary_color' => 'Color primario de la aplicación',
@@ -35,25 +38,23 @@ return [
     'app_homepage_desc' => 'Seleccione una página de inicio para mostrar en lugar de la vista por defecto. Se ignoran los permisos de página para las páginas seleccionadas.',
     'app_homepage_select' => 'Seleccione una página',
     'app_disable_comments' => 'Deshabilitar comentarios',
+    'app_disable_comments_toggle' => 'Deshabilitar comentarios',
     'app_disable_comments_desc' => 'Deshabilitar comentarios en todas las páginas de la aplicación. Los comentarios existentes no se muestran.',
 
-    /**
-     * Registration settings
-     */
-
+    // Registration Settings
     'reg_settings' => 'Ajustes de registro',
-    'reg_allow' => '¿Permitir registro?',
+    'reg_enable' => 'Habilitar Registro',
+    'reg_enable_toggle' => 'Habilitar registro',
+    'reg_enable_desc' => 'Cuando se habilita el registro, el usuario podrá crear su usuario en la aplicación. Con el regsitro, se le otorga un rol de usuario único y por defecto.',
     'reg_default_role' => 'Rol de usuario por defecto despúes del registro',
-    'reg_confirm_email' => '¿Requerir correo electrónico de confirmación?',
+    'reg_email_confirmation' => 'Confirmación de correo electrónico',
+    'reg_email_confirmation_toggle' => 'Requerir confirmación de correo electrónico',
     'reg_confirm_email_desc' => 'Si se utiliza la restricción por dominio, entonces se requerirá la confirmación por correo electrónico y se ignorará el valor a continuación.',
     'reg_confirm_restrict_domain' => 'Restringir registro al dominio',
     'reg_confirm_restrict_domain_desc' => 'Introduzca una lista separada por comas de los correos electrónicos del dominio a los que les gustaría restringir el registro por dominio. A los usuarios les será enviado un correo elctrónico para confirmar la dirección antes de que se le permita interactuar con la aplicación. <br> Note que a los usuarios se les permitirá cambiar sus direcciones de correo electrónico luego de un registro éxioso.',
     'reg_confirm_restrict_domain_placeholder' => 'Ninguna restricción establecida',
 
-    /**
-     * Maintenance settings
-     */
-
+    // Maintenance settings
     'maint' => 'Mantenimiento',
     'maint_image_cleanup' => 'Limpiar imágenes',
     'maint_image_cleanup_desc' => "Analizar contenido de páginas y revisiones para detectar cuáles imágenes y dibujos están en uso y cuáles son redundantes. Asegúrese de crear un respaldo completo de imágenes y base de datos antes de ejecutar esta tarea.",
@@ -63,10 +64,7 @@ return [
     'maint_image_cleanup_success' => 'Se encontraron y se eliminaron :count imágenes pontencialmente sin uso!',
     'maint_image_cleanup_nothing_found' => 'No se encotraron imágenes sin usar, Nada eliminado!',
 
-    /**
-     * Role settings
-     */
-
+    // Role Settings
     'roles' => 'Roles',
     'role_user_roles' => 'Roles de usuario',
     'role_create' => 'Crear nuevo rol',
@@ -86,7 +84,9 @@ return [
     'role_manage_users' => 'Gestionar usuarios',
     'role_manage_roles' => 'Gestionar roles y permisos de roles',
     'role_manage_entity_permissions' => 'Gestionar todos los permisos de libros, capítulos y páginas',
-    'role_manage_own_entity_permissions' => 'Gestionar permisos en libros propios, capítulos y páginas',
+    'role_manage_own_entity_permissions' => 'Gestionar permisos en libro
+    s propios, capítulos y páginas',
+    'role_manage_page_templates' => 'Gestionar las plantillas de páginas',
     'role_manage_settings' => 'Gestionar ajustes de activos',
     'role_asset' => 'Permisos de activos',
     'role_asset_desc' => 'Estos permisos controlan el acceso por defecto a los activos del sistema. Permisos a Libros, Capítulos y Páginas sobreescribiran estos permisos.',
@@ -99,16 +99,22 @@ return [
     'role_users' => 'Usuarios en este rol',
     'role_users_none' => 'No hay usuarios asignados a este rol',
 
-    /**
-     * Users
-     */
-
+    // Users
     'users' => 'Usuarios',
     'user_profile' => 'Perfil de usuario',
     'users_add_new' => 'Agregar nuevo usuario',
     'users_search' => 'Buscar usuarios',
+    'users_details' => 'Detalles del usuario',
+    'users_details_desc' => 'Asigne un nombre de visualización y una dirección de correo electrónico para este usuario. La dirección de correo electrónico se usará pra ingresar a la aplicación.',
+    'users_details_desc_no_email' => 'Asigne un nombre de visualización a este usuario para que los demás puedan reconocerlo.',
     'users_role' => 'Roles de usuario',
+    'users_role_desc' => 'Selecciona los roles a los que será asignado este usuario. Si se asignan varios roles los permisos se acumularán y recibirá todas las habilidades de los roles asignados.',
+    'users_password' => 'Contraseña de Usuario',
+    'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 5 characters long.',
+    'users_send_invite_text' => 'Puede optar por enviar a este usuario un correo electrónico de invitación que les permita establecer su propia contraseña; de lo contrario, puede establecerla contraseña usted mismo.',
+    'users_send_invite_option' => 'Enviar correo electrónico de invitación al usuario.',
     'users_external_auth_id' => 'ID externo de autenticación',
+    'users_external_auth_id_desc' => 'Esta es la ID usada para asociar este usuario con LDAP.',
     'users_password_warning' => 'Solo rellene a continuación si desea cambiar su password:',
     'users_system_public' => 'Este usuario representa cualquier usuario invitado que visita la aplicación. No puede utilizarse para hacer login sino que es asignado automáticamente.',
     'users_delete' => 'Borrar usuario',
@@ -122,6 +128,7 @@ return [
     'users_avatar' => 'Avatar del usuario',
     'users_avatar_desc' => 'Esta imagen debe ser de aproximadamente 256px por lado.',
     'users_preferred_language' => 'Lenguaje preferido',
+    'users_preferred_language_desc' => 'Esta opción cambiará el idioma de la interfaz de usuario en la aplicación. No afectará al contenido creado por los usuarios.',
     'users_social_accounts' => 'Cuentas sociales',
     'users_social_accounts_info' => 'Aquí puede conectar sus otras cuentas para un acceso rápido y más fácil. Desconectando una cuenta aquí no revoca accesos ya autorizados. Revoque el acceso desde los ajustes de perfil en la cuenta social conectada.',
     'users_social_connect' => 'Conectar cuenta',
@@ -129,4 +136,32 @@ return [
     'users_social_connected' => 'La cuenta :socialAccount ha sido exitosamente añadida a su perfil.',
     'users_social_disconnected' => 'La cuenta :socialAccount ha sido desconectada exitosamente de su perfil.',
 
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index 76bb03f0d3cef3d65d59b0e66274599eb5e39d23..cd360c8eae64e9f987a379366f40b10cf7bbfcc5 100644 (file)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => 'El :attribute debe ser aceptado.',
     'active_url'           => 'El :attribute no es una URl válida.',
     'after'                => 'El :attribute debe ser una fecha posterior :date.',
@@ -35,12 +30,41 @@ return [
     'digits'               => ':attribute debe ser de :digits dígitos.',
     'digits_between'       => ':attribute debe ser un valor entre :min y :max dígios.',
     'email'                => ':attribute debe ser una dirección álida.',
+    'ends_with' => 'El :attribute debe terminar con uno de los siguientes: :values',
     'filled'               => 'El campo :attribute es requerido.',
+    'gt'                   => [
+        'numeric' => 'El :attribute debe ser mayor que :value.',
+        'file'    => 'El :attribute debe ser mayor que :value kilobytes.',
+        'string'  => 'El :attribute debe ser mayor que :value caracteres.',
+        'array'   => 'El :attribute debe tener más de :value objetos.',
+    ],
+    'gte'                  => [
+        'numeric' => 'El :attribute debe ser mayor o igual a :value.',
+        'file'    => 'El :attribute debe ser mayor o igual a :value kilobytes.',
+        'string'  => 'El :attribute debe ser mayor o igual a :value caracteres.',
+        'array'   => 'El :attribute debe tener :value objetos o más.',
+    ],
     'exists'               => 'El :attribute seleccionado es inválido.',
     'image'                => 'El :attribute debe ser una imagen.',
+    'image_extension'      => 'El :attribute debe tener una extensión de imagen válida y soportada.',
     'in'                   => 'El selected :attribute es inválio.',
     'integer'              => 'El :attribute debe ser un entero.',
     'ip'                   => 'El :attribute debe ser una dirección IP álida.',
+    'ipv4'                 => 'El :attribute debe ser una dirección IPv4 válida.',
+    'ipv6'                 => 'El :attribute debe ser una dirección IPv6 válida.',
+    'json'                 => 'El :attribute debe ser una cadena JSON válida.',
+    'lt'                   => [
+        'numeric' => 'El :attribute debe ser menor que :value.',
+        'file'    => 'El :attribute debe ser menor que :value kilobytes.',
+        'string'  => 'El :attribute debe ser menor que :value caracteres.',
+        'array'   => 'El :attribute debe tener menos de :value objetos.',
+    ],
+    'lte'                  => [
+        'numeric' => 'El :attribute debe ser menor o igual a :value.',
+        'file'    => 'El :attribute debe ser menor o igual a :value kilobytes.',
+        'string'  => 'El :attribute debe ser menor o igual a :value caracteres.',
+        'array'   => 'El :attribute no debe tener más de :value objetos.',
+    ],
     'max'                  => [
         'numeric' => ':attribute no puede ser mayor que :max.',
         'file'    => ':attribute no puede ser mayor que :max kilobytes.',
@@ -54,7 +78,9 @@ return [
         'string'  => ':attribute debe ser al menos :min caracteres.',
         'array'   => ':attribute debe tener como mínimo :min items.',
     ],
-    'not_in'               => ':attribute seleccionado es inválio.',
+    'no_double_extension'  => 'El :attribute debe tener una única extensión de archivo.',
+    'not_in'               => ':attribute seleccionado es inválido.',
+    'not_regex'            => 'El formato de :attribute es inválido.',
     'numeric'              => ':attribute debe ser numérico.',
     'regex'                => ':attribute con formato inválido',
     'required'             => ':attribute es requerido.',
@@ -73,37 +99,16 @@ return [
     'string'               => 'El atributo :attribute debe ser una cadena.',
     'timezone'             => 'El atributo :attribute debe ser una zona válida.',
     'unique'               => 'El atributo :attribute ya ha sido tomado.',
-    'url'                  => 'El atributo :attribute tiene un formato inválid.',
-    'is_image'             => 'El atributo :attribute debe ser una imagen válida.',
-
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
+    'url'                  => 'El atributo :attribute tiene un formato inválido.',
+    'uploaded'             => 'El archivo no se pudo subir. Puede ser que el servidor no acepte archivos de este tamaño.',
 
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => 'Confirmación de Password requerida',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
diff --git a/resources/lang/format.php b/resources/lang/format.php
deleted file mode 100755 (executable)
index 45d0b48..0000000
+++ /dev/null
@@ -1,330 +0,0 @@
-#!/usr/bin/env php
-<?php
-
-/**
- * Format a language file in the same way as the EN equivalent.
- * Matches the line numbers of translated content.
- * Potentially destructive, Ensure you have a backup of your translation content before running.
- */
-
-$args = array_slice($argv, 1);
-
-if (count($args) < 2) {
-    errorOut("Please provide a language code as the first argument and a translation file name, or '--all', as the second (./format.php fr activities)");
-}
-
-$lang = formatLocale($args[0]);
-$fileName = explode('.', $args[1])[0];
-$fileNames = [$fileName];
-if ($fileName === '--all') {
-    $fileNames = getTranslationFileNames();
-}
-
-foreach ($fileNames as $fileName) {
-    $formatted = formatFileContents($lang, $fileName);
-    writeLangFile($lang, $fileName, $formatted);
-}
-
-
-/**
- * Format the contents of a single translation file in the given language.
- * @param string $lang
- * @param string $fileName
- * @return string
- */
-function formatFileContents(string $lang, string $fileName) : string {
-    $enLines = loadLangFileLines('en', $fileName);
-    $langContent = loadLang($lang, $fileName);
-    $enContent = loadLang('en', $fileName);
-
-    // Calculate the longest top-level key length
-    $longestKeyLength = calculateKeyPadding($enContent);
-
-    // Start formatted content
-    $formatted = [];
-    $mode = 'header';
-    $skipArray = false;
-    $arrayKeys = [];
-
-    foreach ($enLines as $index => $line) {
-        $trimLine = trim($line);
-        if ($mode === 'header') {
-            $formatted[$index] = $line;
-            if (str_replace(' ', '', $trimLine) === 'return[') $mode = 'body';
-        }
-
-        if ($mode === 'body') {
-            $matches = [];
-            $arrayEndMatch = preg_match('/]\s*,\s*$/', $trimLine);
-
-            if ($skipArray) {
-                if ($arrayEndMatch) $skipArray = false;
-                continue;
-            }
-
-            // Comment to ignore
-            if (strpos($trimLine, '//!') === 0) {
-                $formatted[$index] = "";
-                continue;
-            }
-
-            // Comment
-            if (strpos($trimLine, '//') === 0) {
-                $formatted[$index] = "\t" . $trimLine;
-                continue;
-            }
-
-            // Arrays
-            $arrayStartMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\[(\],)?\s*?$/', $trimLine, $matches);
-
-            $indent = count($arrayKeys) + 1;
-            if ($arrayStartMatch === 1) {
-                if ($fileName === 'settings' && $matches[1] === 'language_select') {
-                    $skipArray = true;
-                    continue;
-                }
-                $arrayKeys[] = $matches[1];
-                $formatted[$index] = str_repeat(" ", $indent * 4) . str_pad("'{$matches[1]}'", $longestKeyLength) . "=> [";
-                if ($arrayEndMatch !== 1) continue;
-            }
-            if ($arrayEndMatch === 1) {
-                unsetArrayByKeys($langContent, $arrayKeys);
-                array_pop($arrayKeys);
-                if (isset($formatted[$index])) {
-                    $formatted[$index] .= '],';
-                } else {
-                    $formatted[$index] = str_repeat(" ", ($indent-1) * 4) . "],";
-                }
-                continue;
-            }
-
-            // Translation
-            $translationMatch = preg_match('/^\'(.*)\'\s+?=>\s+?[\'"](.*)?[\'"].+?$/', $trimLine, $matches);
-            if ($translationMatch === 1) {
-                $key = $matches[1];
-                $keys = array_merge($arrayKeys, [$key]);
-                $langVal = getTranslationByKeys($langContent, $keys);
-                if (empty($langVal)) continue;
-
-                $keyPad = $longestKeyLength;
-                if (count($arrayKeys) === 0) {
-                    unset($langContent[$key]);
-                } else {
-                    $keyPad = calculateKeyPadding(getTranslationByKeys($enContent, $arrayKeys));
-                }
-
-                $formatted[$index] = formatTranslationLine($key, $langVal, $indent, $keyPad);
-                continue;
-            }
-        }
-
-    }
-
-    // Fill missing lines
-    $arraySize = max(array_keys($formatted));
-    $formatted = array_replace(array_fill(0, $arraySize, ''), $formatted);
-
-    // Add remaining translations
-    $langContent = array_filter($langContent, function($item) {
-        return !is_null($item) && !empty($item);
-    });
-    if (count($langContent) > 0) {
-        $formatted[] = '';
-        $formatted[] = "\t// Unmatched";
-    }
-    foreach ($langContent as $key => $value) {
-        if (is_array($value)) {
-            $formatted[] = formatTranslationArray($key, $value);
-        } else {
-            $formatted[] = formatTranslationLine($key, $value);
-        }
-    }
-
-    // Add end line
-    $formatted[] = '];';
-    return implode("\n", $formatted);
-}
-
-/**
- * Format a translation line.
- * @param string $key
- * @param string $value
- * @param int $indent
- * @param int $keyPad
- * @return string
- */
-function formatTranslationLine(string $key, string $value, int $indent = 1, int $keyPad = 1) : string {
-    $start = str_repeat(" ", $indent * 4) . str_pad("'{$key}'", $keyPad, ' ');
-    if (strpos($value, "\n") !== false) {
-        $escapedValue = '"' .  str_replace("\n", '\n', $value)  . '"';
-        $escapedValue = '"' .  str_replace('"', '\"', $escapedValue)  . '"';
-    } else {
-        $escapedValue = "'" . str_replace("'", "\\'", $value) . "'";
-    }
-    return "{$start} => {$escapedValue},";
-}
-
-/**
- * Find the longest key in the array and provide the length
- * for all keys to be used when printed.
- * @param array $array
- * @return int
- */
-function calculateKeyPadding(array $array) : int {
-    $top = 0;
-    foreach ($array as $key => $value) {
-        $keyLen = strlen($key);
-        $top = max($top, $keyLen);
-    }
-    return min(35, $top + 2);
-}
-
-/**
- * Format an translation array with the given key.
- * Simply prints as an old-school php array.
- * Used as a last-resort backup to save unused translations.
- * @param string $key
- * @param array $array
- * @return string
- */
-function formatTranslationArray(string $key, array $array) : string {
-    $arrayPHP = var_export($array, true);
-    return "    '{$key}' => {$arrayPHP},";
-}
-
-/**
- * Find a string translation value within a multi-dimensional array
- * by traversing the given array of keys.
- * @param array $translations
- * @param array $keys
- * @return string|array
- */
-function getTranslationByKeys(array $translations, array $keys)  {
-    $val = $translations;
-    foreach ($keys as $key) {
-        $val = $val[$key] ?? '';
-        if ($val === '') return '';
-    }
-    return $val;
-}
-
-/**
- * Unset an inner item of a multi-dimensional array by
- * traversing the given array of keys.
- * @param array $input
- * @param array $keys
- */
-function unsetArrayByKeys(array &$input, array $keys) {
-    $val = &$input;
-    $lastIndex = count($keys) - 1;
-    foreach ($keys as $index => &$key) {
-        if ($index === $lastIndex && is_array($val)) {
-            unset($val[$key]);
-        }
-        if (!is_array($val)) return;
-        $val = &$val[$key] ?? [];
-    }
-}
-
-/**
- * Write the given content to a translation file.
- * @param string $lang
- * @param string $fileName
- * @param string $content
- */
-function writeLangFile(string $lang, string $fileName, string $content) {
-    $path = __DIR__ . "/{$lang}/{$fileName}.php";
-    if (!file_exists($path)) {
-        errorOut("Expected translation file '{$path}' does not exist");
-    }
-    file_put_contents($path, $content);
-}
-
-/**
- * Load the contents of a language file as an array of text lines.
- * @param string $lang
- * @param string $fileName
- * @return array
- */
-function loadLangFileLines(string $lang, string $fileName) : array {
-    $path = __DIR__ . "/{$lang}/{$fileName}.php";
-    if (!file_exists($path)) {
-        errorOut("Expected translation file '{$path}' does not exist");
-    }
-    $lines = explode("\n", file_get_contents($path));
-    return array_map(function($line) {
-        return trim($line, "\r");
-    }, $lines);
-}
-
-/**
- * Load the contents of a language file
- * @param string $lang
- * @param string $fileName
- * @return array
- */
-function loadLang(string $lang, string $fileName) : array {
-    $path = __DIR__ . "/{$lang}/{$fileName}.php";
-    if (!file_exists($path)) {
-        errorOut("Expected translation file '{$path}' does not exist");
-    }
-
-    $fileData = include($path);
-    return $fileData;
-}
-
-/**
- * Fetch an array containing the names of all translation files without the extension.
- * @return array
- */
-function getTranslationFileNames() : array {
-    $dir = __DIR__ . "/en";
-    if (!file_exists($dir)) {
-        errorOut("Expected directory '{$dir}' does not exist");
-    }
-    $files = scandir($dir);
-    $fileNames = [];
-    foreach ($files as $file) {
-        if (substr($file, -4) === '.php') {
-            $fileNames[] = substr($file, 0, strlen($file) - 4);
-        }
-    }
-    return $fileNames;
-}
-
-/**
- * Format a locale to follow the lowercase_UPERCASE standard
- * @param string $lang
- * @return string
- */
-function formatLocale(string $lang) : string {
-    $langParts = explode('_', strtoupper($lang));
-    $langParts[0] = strtolower($langParts[0]);
-    return implode('_', $langParts);
-}
-
-/**
- * Dump a variable then die.
- * @param $content
- */
-function dd($content) {
-    print_r($content);
-    exit(1);
-}
-
-/**
- * Log out some information text in blue
- * @param $text
- */
-function info($text) {
-    echo "\e[34m" . $text . "\e[0m\n";
-}
-
-/**
- * Log out an error in red and exit.
- * @param $text
- */
-function errorOut($text) {
-    echo "\e[31m" . $text . "\e[0m\n";
-    exit(1);
-}
\ No newline at end of file
index ab54bff7cecae3dedae39f90883d3f9a2b6e1a62..56db4abff0490f3c68e97b24933aa925e2b2b586 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => 'a créé la page',
     'page_create_notification'    => 'Page créée avec succès',
@@ -38,7 +36,7 @@ return [
     'book_sort_notification'      => 'Livre réordonné avec succès',
 
     // Bookshelves
-    'bookshelf_create'                 => 'a créé l\'étagère',
+    'bookshelf_create'            => 'a créé l\'étagère',
     'bookshelf_create_notification'    => 'Étagère créée avec succès',
     'bookshelf_update'                 => 'a modifié l\'étagère',
     'bookshelf_update_notification'    => 'Étagère modifiée avec succès',
@@ -46,5 +44,5 @@ return [
     'bookshelf_delete_notification'    => 'Étagère supprimée avec succès',
 
     // Other
-    'commented_on'                => 'a commenté'
+    'commented_on'                => 'a commenté',
 ];
index c9ce6a4d74d18fb0d725236f6dfa45613ca9378b..99b41c070d3a06265764f91fe591bf4863ba1b2b 100644 (file)
@@ -1,73 +1,62 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => 'Ces informations ne correspondent à aucun compte.',
-    'throttle' => "Trop d'essais, veuillez réessayer dans :seconds secondes.",
+    'throttle' => 'Trop d\'essais, veuillez réessayer dans :seconds secondes.',
 
-    /**
-     * Login & Register
-     */
-    'sign_up' => "S'inscrire",
+    // Login & Register
+    'sign_up' => 'S\'inscrire',
     'log_in' => 'Se connecter',
     'log_in_with' => 'Se connecter avec :socialDriver',
     'sign_up_with' => 'S\'inscrire avec :socialDriver',
     'logout' => 'Se déconnecter',
 
     'name' => 'Nom',
-    'username' => "Nom d'utilisateur",
+    'username' => 'Nom d\'utilisateur',
     'email' => 'E-mail',
     'password' => 'Mot de passe',
     'password_confirm' => 'Confirmez le mot de passe',
-    'password_hint' => 'Doit faire plus de 5 caractères',
-    'forgot_password' => 'Mot de passe oublié ?',
+    'password_hint' => 'Doit faire plus de 7 caractères',
+    'forgot_password' => 'Mot de passe oublié ?',
     'remember_me' => 'Se souvenir de moi',
-    'ldap_email_hint' => "Merci d'entrer une adresse e-mail pour ce compte",
+    'ldap_email_hint' => 'Merci d\'entrer une adresse e-mail pour ce compte.',
     'create_account' => 'Créer un compte',
-    'already_have_account' => 'Vous avez déjà un compte?',
-    'dont_have_account' => 'Vous n\'avez pas de compte?',
-    'social_login' => 'Social Login',
-    'social_registration' => 'Enregistrement Social',
-    'social_registration_text' => "S'inscrire et se connecter avec un réseau social",
+    'already_have_account' => 'Vous avez déjà un compte ?',
+    'dont_have_account' => 'Vous n\'avez pas de compte ?',
+    'social_login' => 'Connexion avec un réseau social',
+    'social_registration' => 'Inscription avec un réseau social',
+    'social_registration_text' => 'S\'inscrire et se connecter avec un réseau social.',
 
-    'register_thanks' => 'Merci pour votre enregistrement',
+    'register_thanks' => 'Merci pour votre inscription !',
     'register_confirm' => 'Vérifiez vos e-mails et cliquez sur le lien de confirmation pour rejoindre :appName.',
-    'registrations_disabled' => "L'inscription est désactivée pour le moment",
+    'registrations_disabled' => 'Les inscriptions sont désactivées pour le moment',
     'registration_email_domain_invalid' => 'Cette adresse e-mail ne peut pas accéder à l\'application',
     'register_success' => 'Merci pour votre inscription. Vous êtes maintenant inscrit(e) et connecté(e)',
 
 
-    /**
-     * Password Reset
-     */
-    'reset_password' => 'Reset Password',
-    'reset_password_send_instructions' => 'Entrez votre adresse e-mail ci-dessous et un e-mail avec un lien de réinitialisation de mot de passe vous sera envoyé',
+    // Password Reset
+    'reset_password' => 'Réinitialiser le mot de passe',
+    'reset_password_send_instructions' => 'Entrez votre adresse e-mail ci-dessous et un e-mail avec un lien de réinitialisation de mot de passe vous sera envoyé.',
     'reset_password_send_button' => 'Envoyer un lien de réinitialisation',
     'reset_password_sent_success' => 'Un lien de réinitialisation a été envoyé à :email.',
     'reset_password_success' => 'Votre mot de passe a été réinitialisé avec succès.',
-
     'email_reset_subject' => 'Réinitialisez votre mot de passe pour :appName',
-    'email_reset_text' => 'Vous recevez cet e-mail parce que nous avons reçu une demande de réinitialisation pour votre compte',
+    'email_reset_text' => 'Vous recevez cet e-mail parce que nous avons reçu une demande de réinitialisation pour votre compte.',
     'email_reset_not_requested' => 'Si vous n\'avez pas effectué cette demande, vous pouvez ignorer cet e-mail.',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => 'Confirmez votre adresse e-mail pour :appName',
-    'email_confirm_greeting' => 'Merci d\'avoir rejoint :appName !',
-    'email_confirm_text' => 'Merci de confirmer en cliquant sur le lien ci-dessous :',
+    'email_confirm_greeting' => 'Merci d\'avoir rejoint :appName !',
+    'email_confirm_text' => 'Merci de confirmer en cliquant sur le lien ci-dessous :',
     'email_confirm_action' => 'Confirmez votre adresse e-mail',
     'email_confirm_send_error' => 'La confirmation par e-mail est requise mais le système n\'a pas pu envoyer l\'e-mail. Contactez l\'administrateur système.',
-    'email_confirm_success' => 'Votre adresse e-mail a été confirmée !',
+    'email_confirm_success' => 'Votre adresse e-mail a été confirmée !',
     'email_confirm_resent' => 'L\'e-mail de confirmation a été ré-envoyé. Vérifiez votre boîte de récéption.',
 
     'email_not_confirmed' => 'Adresse e-mail non confirmée',
@@ -75,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Merci de cliquer sur le lien dans l\'e-mail qui vous a été envoyé après l\'enregistrement.',
     'email_not_confirmed_resend' => 'Si vous ne retrouvez plus l\'e-mail, vous pouvez renvoyer un e-mail de confirmation en utilisant le formulaire ci-dessous.',
     'email_not_confirmed_resend_button' => 'Renvoyez l\'e-mail de confirmation',
-];
+
+    // User Invite
+    'user_invite_email_subject' => 'Vous avez été invité(e) à rejoindre :appName !',
+    'user_invite_email_greeting' => 'Un compte vous a été créé sur :appName.',
+    'user_invite_email_text' => 'Cliquez sur le bouton ci-dessous pour renseigner le mot de passe et récupérer l\'accès :',
+    'user_invite_email_action' => 'Renseignez le mot de passe de votre compte',
+    'user_invite_page_welcome' => 'Bienvenue dans :appName !',
+    'user_invite_page_text' => 'Pour finaliser votre compte et recevoir l\'accès, vous devez renseigner le mot de passe qui sera utilisé pour la connexion à :appName les prochaines fois.',
+    'user_invite_page_confirm_button' => 'Confirmez le mot de passe',
+    'user_invite_success' => 'Mot de passe renseigné, vous avez maintenant accès à :appName !'
+];
\ No newline at end of file
index 1cf6e716f87f3e3962adae680ed868e96528ace6..3fb19a303949f83dff8d8bbf9ff6f403c598bc41 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => 'Annuler',
     'confirm' => 'Confirmer',
     'back' => 'Retour',
@@ -13,18 +14,14 @@ return [
     'toggle_all' => 'Tout sélectionner',
     'more' => 'Montrer plus',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => 'Nom',
     'description' => 'Description',
     'role' => 'Rôle',
     'cover_image' => 'Image de couverture',
-    'cover_image_description' => 'Cette image doit être environ 440x250px.',
+    'cover_image_description' => 'Cette image doit faire environ 440x250 px.',
     
-    /**
-     * Actions
-     */
+    // Actions
     'actions' => 'Actions',
     'view' => 'Voir',
     'view_all' => 'Tout afficher',
@@ -42,16 +39,16 @@ return [
     'remove' => 'Enlever',
     'add' => 'Ajouter',
 
-    /**
-     * Sort Options
-     */
+    // Sort Options
+    'sort_options' => 'Options de tri',
+    'sort_direction_toggle' => 'Inverser la direction du tri',
+    'sort_ascending' => 'Tri ascendant',
+    'sort_descending' => 'Tri descendant',
     'sort_name' => 'Nom',
     'sort_created_at' => 'Date de création',
     'sort_updated_at' => 'Date de mise à jour',
 
-    /**
-     * Misc
-     */
+    // Misc
     'deleted_user' => 'Utilisateur supprimé',
     'no_activity' => 'Aucune activité',
     'no_items' => 'Aucun élément',
@@ -62,16 +59,18 @@ return [
     'grid_view' => 'Vue en grille',
     'list_view' => 'Vue en liste',
     'default' => 'Défaut',
+    'breadcrumb' => 'Fil d\'Ariane',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Menu du profil',
     'view_profile' => 'Voir le profil',
     'edit_profile' => 'Modifier le profil',
 
-    /**
-     * Email Content
-     */
-    'email_action_help' => 'Si vous rencontrez des problèmes pour cliquer sur le bouton ":actionText", copiez et collez l\'adresse ci-dessous dans votre navigateur :',
+    // Layout tabs
+    'tab_info' => 'Info',
+    'tab_content' => 'Contenu',
+
+    // Email Content
+    'email_action_help' => 'Si vous rencontrez des problèmes pour cliquer sur le bouton ":actionText", copiez et collez l\'adresse ci-dessous dans votre navigateur :',
     'email_rights' => 'Tous droits réservés',
 ];
index e314536c7969c1627ffd789d18b96fa1aa502146..2f6ff8bf9c906413e5a679c4943b6364d9ea8633 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => 'Sélectionner une image',
     'image_all' => 'Toutes',
     'image_all_title' => 'Voir toutes les images',
@@ -24,9 +25,7 @@ return [
     'image_delete_success' => 'Image supprimée avec succès',
     'image_upload_remove' => 'Supprimer',
 
-    /**
-     * Code editor
-     */
+    // Code Editor
     'code_editor' => 'Editer le code',
     'code_language' => 'Langage du code',
     'code_content' => 'Contenu du code',
index 4ba1a36e3df9ceb8b059e61c16b43bff91f85e9c..a6c665f9c1f5904a6437832d62763bb71e19bbf7 100644 (file)
@@ -1,9 +1,11 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => 'Créé récemment',
     'recently_created_pages' => 'Pages créées récemment',
     'recently_updated_pages' => 'Pages mises à jour récemment',
@@ -32,17 +34,13 @@ return [
     'export_pdf' => 'Fichier PDF',
     'export_text' => 'Document texte',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => 'Permissions',
     'permissions_intro' => 'Une fois activées ces permissions prendront la priorité sur tous les sets de permissions préexistants.',
     'permissions_enable' => 'Activer les permissions personnalisées',
     'permissions_save' => 'Enregistrer les permissions',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => 'Résultats de recherche',
     'search_total_results_found' => ':count résultats trouvés|:count résultats trouvés au total',
     'search_clear' => 'Réinitialiser la recherche',
@@ -67,9 +65,7 @@ return [
     'search_set_date' => 'Choisir la date',
     'search_update' => 'Actualiser la recherche',
 
-    /**
-     * Shelves
-     */
+    // Shelves
     'shelf' => 'Étagère',
     'shelves' => 'Étagères',
     'x_shelves' => ':count Étagère|:count Étagères',
@@ -91,8 +87,8 @@ return [
     'shelves_edit' => 'Modifier l\'étagère',
     'shelves_delete' => 'Supprimer l\'étagère',
     'shelves_delete_named' => 'Supprimer l\'étagère :name',
-    'shelves_delete_explain' => "Ceci va supprimer l\'étagère nommée \':bookName\'. Les livres contenus dans cette étagère ne seront pas supprimés.",
-    'shelves_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer cette étagère ?',
+    'shelves_delete_explain' => "Ceci va supprimer l\\'étagère nommée \\':bookName\\'. Les livres contenus dans cette étagère ne seront pas supprimés.",
+    'shelves_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer cette étagère ?',
     'shelves_permissions' => 'Permissions de l\'étagère',
     'shelves_permissions_updated' => 'Permissions de l\'étagère mises à jour',
     'shelves_permissions_active' => 'Permissions de l\'étagère activées',
@@ -101,9 +97,7 @@ return [
     'shelves_copy_permissions_explain' => 'Ceci va appliquer les permissions actuelles de cette étagère à tous les livres qu\'elle contient. Avant de  continuer, assurez-vous que toutes les permissions de cette étagère ont été sauvegardées.',
     'shelves_copy_permission_success' => 'Permissions de l\'étagère transférées à :count livres',
 
-    /**
-     * Books
-     */
+    // Books
     'book' => 'Livre',
     'books' => 'Livres',
     'x_books' => ':count livre|:count livres',
@@ -118,7 +112,7 @@ return [
     'books_delete' => 'Supprimer un livre',
     'books_delete_named' => 'Supprimer le livre :bookName',
     'books_delete_explain' => 'Ceci va supprimer le livre nommé \':bookName\', tous les chapitres et pages seront supprimés.',
-    'books_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer ce livre ?',
+    'books_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer ce livre ?',
     'books_edit' => 'Modifier le livre',
     'books_edit_named' => 'Modifier le livre :bookName',
     'books_form_book_name' => 'Nom du livre',
@@ -127,7 +121,6 @@ return [
     'books_permissions_updated' => 'Permissions du livre mises à jour',
     'books_empty_contents' => 'Aucune page ou chapitre n\'a été ajouté à ce livre.',
     'books_empty_create_page' => 'Créer une nouvelle page',
-    'books_empty_or' => 'ou',
     'books_empty_sort_current_book' => 'Trier les pages du livre',
     'books_empty_add_chapter' => 'Ajouter un chapitre',
     'books_permissions_active' => 'Permissions personnalisées activées',
@@ -143,9 +136,7 @@ return [
     'books_sort_show_other' => 'Afficher d\'autres livres',
     'books_sort_save' => 'Enregistrer l\'ordre',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => 'Chapitre',
     'chapters' => 'Chapitres',
     'x_chapters' => ':count chapitre|:count chapitres',
@@ -155,7 +146,7 @@ return [
     'chapters_delete' => 'Supprimer le chapitre',
     'chapters_delete_named' => 'Supprimer le chapitre :chapterName',
     'chapters_delete_explain' => 'Ceci va supprimer le chapitre \':chapterName\', toutes les pages seront déplacées dans le livre parent.',
-    'chapters_delete_confirm' => 'Etes-vous sûr(e) de vouloir supprimer ce chapitre ?',
+    'chapters_delete_confirm' => 'Etes-vous sûr(e) de vouloir supprimer ce chapitre ?',
     'chapters_edit' => 'Modifier le chapitre',
     'chapters_edit_named' => 'Modifier le chapitre :chapterName',
     'chapters_save' => 'Enregistrer le chapitre',
@@ -168,9 +159,7 @@ return [
     'chapters_permissions_success' => 'Permissions du chapitre mises à jour',
     'chapters_search_this' => 'Rechercher dans ce chapitre',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => 'Page',
     'pages' => 'Pages',
     'x_pages' => ':count Page|:count Pages',
@@ -184,10 +173,10 @@ return [
     'pages_delete_draft' => 'Supprimer le brouillon',
     'pages_delete_success' => 'Page supprimée',
     'pages_delete_draft_success' => 'Brouillon supprimé',
-    'pages_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cette page ?',
-    'pages_delete_draft_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer ce brouillon ?',
+    'pages_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cette page ?',
+    'pages_delete_draft_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer ce brouillon ?',
     'pages_editing_named' => 'Modification de la page :pageName',
-    'pages_edit_toggle_header' => 'Afficher/cacher l\'en-tête',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'Enregistrer le brouillon',
     'pages_edit_draft' => 'Modifier le brouillon',
     'pages_editing_draft' => 'Modification du brouillon',
@@ -221,6 +210,8 @@ return [
     'pages_revisions_created_by' => 'Créé par',
     'pages_revisions_date' => 'Date de révision',
     'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
     'pages_revisions_changelog' => 'Journal des changements',
     'pages_revisions_changes' => 'Changements',
     'pages_revisions_current' => 'Version courante',
@@ -239,23 +230,24 @@ return [
         'start_b' => ':userName a commencé à éditer cette page',
         'time_a' => 'depuis la dernière sauvegarde',
         'time_b' => 'dans les :minCount dernières minutes',
-        'message' => ':start :time. Attention à ne pas écraser les mises à jour de quelqu\'un d\'autre !',
+        'message' => ':start :time. Attention à ne pas écraser les mises à jour de quelqu\'un d\'autre !',
     ],
     'pages_draft_discarded' => 'Brouillon écarté, la page est dans sa version actuelle.',
     'pages_specific' => 'Page Spécifique',
+    'pages_is_template' => 'Modèle de page',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => 'Mots-clés de la page',
     'chapter_tags' => 'Mots-clés du chapitre',
     'book_tags' => 'Mots-clés du livre',
     'shelf_tags' => 'Mots-clés de l\'étagère',
     'tag' => 'Mot-clé',
     'tags' =>  'Mots-clés',
+    'tag_name' =>  'Tag Name',
     'tag_value' => 'Valeur du mot-clé (Optionnel)',
     'tags_explain' => "Ajouter des mots-clés pour catégoriser votre contenu.",
     'tags_add' => 'Ajouter un autre mot-clé',
+    'tags_remove' => 'Remove this tag',
     'attachments' => 'Fichiers joints',
     'attachments_explain' => 'Ajouter des fichiers ou des liens pour les afficher sur votre page. Ils seront affichés dans la barre latérale',
     'attachments_explain_instant_save' => 'Ces changements sont enregistrés immédiatement.',
@@ -281,10 +273,14 @@ return [
     'attachments_file_uploaded' => 'Fichier ajouté avec succès',
     'attachments_file_updated' => 'Fichier mis à jour avec succès',
     'attachments_link_attached' => 'Lien attaché à la page avec succès',
+    'templates' => 'Modèles',
+    'templates_set_as_template' => 'La page est un modèle',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Remplacer le contenu de la page',
+    'templates_append_content' => 'Ajouter après le contenu de la page',
+    'templates_prepend_content' => 'Ajouter devant le contenu de la page',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => 'Utilisateur depuis :time',
     'profile_created_content' => 'Contenu créé',
     'profile_not_created_pages' => ':userName n\'a pas créé de page',
@@ -292,30 +288,27 @@ return [
     'profile_not_created_books' => ':userName n\'a pas créé de livre',
     'profile_not_created_shelves' => ':userName n\'a pas créé d\'étagère',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => 'Commentaire',
     'comments' => 'Commentaires',
     'comment_add' => 'Ajouter un commentaire',
     'comment_placeholder' => 'Entrez vos commentaires ici',
-    'comment_count' => '{0} Pas de commentaires|{1} 1 Commentaire|[2,*] :count Commentaires',
+    'comment_count' => '{0} Pas de commentaires|{1} Un commentaire|[2,*] :count commentaires',
     'comment_save' => 'Enregistrer le commentaire',
-    'comment_saving' => 'Enregistrement du commentaire...',
-    'comment_deleting' => 'Suppression du commentaire...',
+    'comment_saving' => 'Enregistrement du commentaire',
+    'comment_deleting' => 'Suppression du commentaire',
     'comment_new' => 'Nouveau commentaire',
     'comment_created' => 'commenté :createDiff',
     'comment_updated' => 'Mis à jour :updateDiff par :username',
     'comment_deleted_success' => 'Commentaire supprimé',
     'comment_created_success' => 'Commentaire ajouté',
     'comment_updated_success' => 'Commentaire mis à jour',
-    'comment_delete_confirm' => 'Etes-vous sûr de vouloir supprimer ce commentaire ?',
+    'comment_delete_confirm' => 'Etes-vous sûr de vouloir supprimer ce commentaire ?',
     'comment_in_reply_to' => 'En réponse à :commentId',
 
-     /**
-     * Revision
-     */
-    'revision_delete_confirm' => 'Êtes-vous sûr de vouloir supprimer cette révision?',
+    // Revision
+    'revision_delete_confirm' => 'Êtes-vous sûr de vouloir supprimer cette révision ?',
+    'revision_restore_confirm' => 'Êtes-vous sûr de vouloir restaurer cette révision ? Le contenu courant de la page va être remplacé.',
     'revision_delete_success' => 'Révision supprimée',
     'revision_cannot_delete_latest' => 'Impossible de supprimer la dernière révision.'
-];
+];
\ No newline at end of file
index e758f6e5e5c7da99c4a3d663dc6430efac561b6c..11da312a44223b5d75bdd435885cda610a0d6b2b 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => 'Vous n\'avez pas les droits pour accéder à cette page.',
     'permissionJson' => 'Vous n\'avez pas les droits pour exécuter cette action.',
@@ -20,7 +18,7 @@ return [
     'ldap_extension_not_installed' => 'L\'extension LDAP PHP n\'est pas installée',
     'ldap_cannot_connect' => 'Impossible de se connecter au serveur LDAP, la connexion initiale a échoué',
     'social_no_action_defined' => 'Pas d\'action définie',
-    'social_login_bad_response' => "Erreur pendant la tentative de connexion à :socialAccount : \n:error",
+    'social_login_bad_response' => "Erreur pendant la tentative de connexion à :socialAccount : \n:error",
     'social_account_in_use' => 'Ce compte :socialAccount est déjà utilisé. Essayez de vous connecter via :socialAccount.',
     'social_account_email_in_use' => 'L\'email :email est déjà utilisé. Si vous avez déjà un compte :socialAccount, vous pouvez le joindre à votre profil existant.',
     'social_account_existing' => 'Ce compte :socialAccount est déjà rattaché à votre profil.',
@@ -29,6 +27,7 @@ return [
     'social_account_register_instructions' => 'Si vous n\'avez pas encore de compte, vous pouvez le lier avec l\'option :socialAccount.',
     'social_driver_not_found' => 'Pilote de compte social absent',
     'social_driver_not_configured' => 'Vos préférences pour le compte :socialAccount sont incorrectes.',
+    'invite_token_expired' => 'Le lien de cette invitation a expiré. Vous pouvez essayer de réinitiliser votre mot de passe.',
 
     // System
     'path_not_writable' => 'Impossible d\'écrire dans :filePath. Assurez-vous d\'avoir les droits d\'écriture sur le serveur',
@@ -66,6 +65,14 @@ return [
     'role_cannot_be_edited' => 'Ce rôle ne peut pas être modifié',
     'role_system_cannot_be_deleted' => 'Ceci est un rôle du système et ne peut pas être supprimé',
     'role_registration_default_cannot_delete' => 'Ce rôle ne peut pas être supprimé tant qu\'il est le rôle par défaut',
+    'role_cannot_remove_only_admin' => 'Ceci est le seul compte administrateur. Assignez un nouvel administrateur avant de le supprimer ici.',
+
+    // Comments
+    'comment_list' => 'Une erreur s\'est produite lors de la récupération des commentaires.',
+    'cannot_add_comment_to_draft' => 'Vous ne pouvez pas ajouter de commentaires à un projet.',
+    'comment_add' => 'Une erreur s\'est produite lors de l\'ajout du commentaire.',
+    'comment_delete' => 'Une erreur s\'est produite lors de la suppression du commentaire.',
+    'empty_comment' => 'Impossible d\'ajouter un commentaire vide.',
 
     // Error pages
     '404_page_not_found' => 'Page non trouvée',
@@ -75,10 +82,4 @@ return [
     'app_down' => ':appName n\'est pas en service pour le moment',
     'back_soon' => 'Nous serons bientôt de retour.',
 
-    // comments
-    'comment_list' => 'Une erreur s\'est produite lors de la récupération des commentaires.',
-    'cannot_add_comment_to_draft' => 'Vous ne pouvez pas ajouter de commentaires à un projet.',
-    'comment_add' => 'Une erreur s\'est produite lors de l\'ajout du commentaire.',
-    'comment_delete' => 'Une erreur s\'est produite lors de la suppression du commentaire.',
-    'empty_comment' => 'Impossible d\'ajouter un commentaire vide.',
 ];
index 9f07a5f937252a1d6f225900e13114c84f1fef28..5d8f102fbe320ebe6587120bfbd0d3b3e0226cf2 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; Précédent',
     'next'     => 'Suivant &raquo;',
 
index 484b4b20c295c6f7862e72cfff6cf3a22e2ee172..3852f5bf1492baaa63355999e9c112daacf6baae 100644 (file)
@@ -1,22 +1,15 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => 'Les mots de passe doivent faire au moins 6 caractères et correspondre à la confirmation.',
     'user' => "Nous n'avons pas trouvé d'utilisateur avec cette adresse.",
     'token' => 'Le jeton de réinitialisation est invalide.',
-    'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe !',
-    'reset' => 'Votre mot de passe a été réinitialisé !',
+    'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe !',
+    'reset' => 'Votre mot de passe a été réinitialisé !',
 
 ];
index f978114c5671ab558f03554bad8ed597429e3c58..fc25fb22c5797e28eaa47dce65838335b74caf7c 100644 (file)
@@ -1,38 +1,35 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
-
+    // Common Messages
     'settings' => 'Préférences',
     'settings_save' => 'Enregistrer les préférences',
     'settings_save_success' => 'Préférences enregistrées',
 
-    /**
-     * App settings
-     */
-
+    // App Settings
     'app_customization' => 'Personnalisation',
     'app_features_security' => 'Fonctionnalités et sécurité',
     'app_name' => 'Nom de l\'application',
     'app_name_desc' => 'Ce nom est affiché dans l\'en-tête et les e-mails.',
-    'app_name_header' => 'Afficher le nom dans l\'en-tête ?',
+    'app_name_header' => 'Afficher le nom dans l\'en-tête ?',
     'app_public_access' => 'Accès public',
     'app_public_access_desc' => 'L\'activation de cette option permettra aux visiteurs, qui ne sont pas connectés, d\'accéder au contenu de votre instance BookStack.',
     'app_public_access_desc_guest' => 'L\'accès pour les visiteurs publics peut être contrôlé par l\'utilisateur "Guest".',
     'app_public_access_toggle' => 'Autoriser l\'accès public',
-    'app_public_viewing' => 'Accepter le visionnage public des pages ?',
-    'app_secure_images' => 'Activer l\'ajout d\'image sécurisé ?',
+    'app_public_viewing' => 'Accepter le visionnage public des pages ?',
+    'app_secure_images' => 'Activer l\'ajout d\'image sécurisé ?',
     'app_secure_images_toggle' => 'Activer l\'ajout d\'image sécurisé',
     'app_secure_images_desc' => 'Pour des questions de performances, toutes les images sont publiques. Cette option ajoute une chaîne aléatoire difficile à deviner dans les URLs des images.',
     'app_editor' => 'Editeur des pages',
     'app_editor_desc' => 'Sélectionnez l\'éditeur qui sera utilisé pour modifier les pages.',
     'app_custom_html' => 'HTML personnalisé dans l\'en-tête',
     'app_custom_html_desc' => 'Le contenu inséré ici sera ajouté en bas de la balise <head> de toutes les pages. Vous pouvez l\'utiliser pour ajouter du CSS personnalisé ou un tracker analytique.',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'Logo de l\'Application',
     'app_logo_desc' => 'Cette image doit faire 43px de hauteur. <br>Les images plus larges seront réduites.',
     'app_primary_color' => 'Couleur principale de l\'application',
@@ -43,40 +40,31 @@ return [
     'app_disable_comments' => 'Désactiver les commentaires',
     'app_disable_comments_toggle' => 'Désactiver les commentaires',
     'app_disable_comments_desc' => 'Désactive les commentaires sur toutes les pages de l\'application. Les commentaires existants ne sont pas affichés.',
-    
-    /**
-     * Registration settings
-     */
 
+    // Registration Settings
     'reg_settings' => 'Préférence pour l\'inscription',
     'reg_enable' => 'Activer l\'inscription',
     'reg_enable_toggle' => 'Activer l\'inscription',
     'reg_enable_desc' => 'Lorsque l\'inscription est activée, l\'utilisateur pourra s\'enregistrer en tant qu\'utilisateur de l\'application. Lors de l\'inscription, ils se voient attribuer un rôle par défaut.',
     'reg_default_role' => 'Rôle par défaut lors de l\'inscription',
     'reg_email_confirmation' => 'Confirmation de l\'e-mail',
-    'reg_email_confirmation_toggle' => 'Obliger la confirmation par e-mail ?',
+    'reg_email_confirmation_toggle' => 'Obliger la confirmation par e-mail ?',
     'reg_confirm_email_desc' => 'Si la restriction de domaine est activée, la confirmation sera automatiquement obligatoire et cette valeur sera ignorée.',
     'reg_confirm_restrict_domain' => 'Restreindre l\'inscription à un domaine',
     'reg_confirm_restrict_domain_desc' => 'Entrez une liste de domaines acceptés lors de l\'inscription, séparés par une virgule. Les utilisateurs recevront un e-mail de confirmation à cette adresse. <br> Les utilisateurs pourront changer leur adresse après inscription s\'ils le souhaitent.',
     'reg_confirm_restrict_domain_placeholder' => 'Aucune restriction en place',
 
-    /**
-     * Maintenance settings
-     */
-
+    // Maintenance settings
     'maint' => 'Maintenance',
     'maint_image_cleanup' => 'Nettoyer les images',
     'maint_image_cleanup_desc' => "Scan le contenu des pages et des révisions pour vérifier les images et les dessins en cours d'utilisation et lesquels sont redondant. Veuillez à faire une sauvegarde de la base de données et des images avant de lancer ceci.",
     'maint_image_cleanup_ignore_revisions' => 'Ignorer les images dans les révisions',
     'maint_image_cleanup_run' => 'Lancer le nettoyage',
-    'maint_image_cleanup_warning' => ':count images potentiellement inutilisées trouvées. Etes-vous sûr de vouloir supprimer ces images ?',
-    'maint_image_cleanup_success' => ':count images potentiellement inutilisées trouvées et supprimées !',
-    'maint_image_cleanup_nothing_found' => 'Aucune image inutilisée trouvée, rien à supprimer !',
-    
-    /**
-     * Role settings
-     */
+    'maint_image_cleanup_warning' => ':count images potentiellement inutilisées trouvées. Etes-vous sûr de vouloir supprimer ces images ?',
+    'maint_image_cleanup_success' => ':count images potentiellement inutilisées trouvées et supprimées !',
+    'maint_image_cleanup_nothing_found' => 'Aucune image inutilisée trouvée, rien à supprimer !',
 
+    // Role Settings
     'roles' => 'Rôles',
     'role_user_roles' => 'Rôles des utilisateurs',
     'role_create' => 'Créer un nouveau rôle',
@@ -85,7 +73,7 @@ return [
     'role_delete_confirm' => 'Ceci va supprimer le rôle \':roleName\'.',
     'role_delete_users_assigned' => 'Ce rôle a :userCount utilisateurs assignés. Vous pouvez choisir un rôle de remplacement pour ces utilisateurs.',
     'role_delete_no_migration' => "Ne pas assigner de nouveau rôle",
-    'role_delete_sure' => 'Êtes-vous sûr de vouloir supprimer ce rôle ?',
+    'role_delete_sure' => 'Êtes-vous sûr de vouloir supprimer ce rôle ?',
     'role_delete_success' => 'Le rôle a été supprimé avec succès',
     'role_edit' => 'Modifier le rôle',
     'role_details' => 'Détails du rôle',
@@ -96,7 +84,8 @@ return [
     'role_manage_users' => 'Gérer les utilisateurs',
     'role_manage_roles' => 'Gérer les rôles et permissions',
     'role_manage_entity_permissions' => 'Gérer les permissions sur les livres, chapitres et pages',
-    'role_manage_own_entity_permissions' => 'Gérer les permissions de ses propres livres, chapitres, et pages',
+    'role_manage_own_entity_permissions' => 'Gérer les permissions de ses propres livres, chapitres et pages',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'Gérer les préférences de l\'application',
     'role_asset' => 'Permissions des ressources',
     'role_asset_desc' => 'Ces permissions contrôlent l\'accès par défaut des ressources dans le système. Les permissions dans les livres, les chapitres et les pages ignoreront ces permissions',
@@ -109,10 +98,7 @@ return [
     'role_users' => 'Utilisateurs ayant ce rôle',
     'role_users_none' => 'Aucun utilisateur avec ce rôle actuellement',
 
-    /**
-     * Users
-     */
-
+    // Users
     'users' => 'Utilisateurs',
     'user_profile' => 'Profil d\'utilisateur',
     'users_add_new' => 'Ajouter un nouvel utilisateur',
@@ -124,6 +110,8 @@ return [
     'users_role_desc' => 'Sélectionnez les rôles auxquels cet utilisateur sera affecté. Si un utilisateur est affecté à plusieurs rôles, les permissions de ces rôles s\'empileront et ils recevront toutes les capacités des rôles affectés.',
     'users_password' => 'Mot de passe de l\'utilisateur',
     'users_password_desc' => 'Définissez un mot de passe utilisé pour vous connecter à l\'application. Il doit comporter au moins 5 caractères.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => 'Identifiant d\'authentification externe',
     'users_external_auth_id_desc' => 'Il s\'agit de l\'identifiant utilisé pour appairer cet utilisateur lors de la communication avec votre système LDAP.',
     'users_password_warning' => 'Remplissez ce formulaire uniquement si vous souhaitez changer de mot de passe:',
@@ -131,13 +119,13 @@ return [
     'users_delete' => 'Supprimer un utilisateur',
     'users_delete_named' => 'Supprimer l\'utilisateur :userName',
     'users_delete_warning' => 'Ceci va supprimer \':userName\' du système.',
-    'users_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cet utilisateur ?',
+    'users_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cet utilisateur ?',
     'users_delete_success' => 'Utilisateurs supprimés avec succès',
     'users_edit' => 'Modifier l\'utilisateur',
     'users_edit_profile' => 'Modifier le profil',
     'users_edit_success' => 'Utilisateur mis à jour avec succès',
     'users_avatar' => 'Avatar de l\'utilisateur',
-    'users_avatar_desc' => 'Cette image doit être un carré d\'environ 256px.',
+    'users_avatar_desc' => 'Cette image doit être un carré d\'environ 256 px.',
     'users_preferred_language' => 'Langue préférée',
     'users_preferred_language_desc' => 'Cette option changera la langue utilisée pour l\'interface utilisateur de l\'application. Ceci n\'affectera aucun contenu créé par l\'utilisateur.',
     'users_social_accounts' => 'Comptes sociaux',
@@ -147,4 +135,32 @@ return [
     'users_social_connected' => 'Votre compte :socialAccount a été ajouté avec succès.',
     'users_social_disconnected' => 'Votre compte :socialAccount a été déconnecté avec succès',
 
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index 4be55df4ff35b240cb446ef700bea86d5989b57c..f59d5c50313c3fb1ea3533b0d27be85e62b70ffb 100644 (file)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => ':attribute doit être accepté.',
     'active_url'           => ':attribute n\'est pas une URL valide.',
     'after'                => ':attribute doit être supérieur à :date.',
@@ -35,13 +30,41 @@ return [
     'digits'               => ':attribute doit être de longueur :digits.',
     'digits_between'       => ':attribute doit avoir une longueur entre :min et :max.',
     'email'                => ':attribute doit être une adresse e-mail valide.',
+    'ends_with' => ':attribute doit se terminer par une des valeurs suivantes : :values',
     'filled'               => ':attribute est un champ requis.',
+    'gt'                   => [
+        'numeric' => ':attribute doit être plus grand que :value.',
+        'file'    => ':attribute doit être plus grand que :value kilobytes.',
+        'string'  => ':attribute doit être plus grand que :value caractères.',
+        'array'   => ':attribute doit avoir plus que :value éléments.',
+    ],
+    'gte'                  => [
+        'numeric' => ':attribute doit être plus grand ou égal à :value.',
+        'file'    => ':attribute doit être plus grand ou égal à :value kilobytes.',
+        'string'  => ':attribute doit être plus grand ou égal à :value caractères.',
+        'array'   => ':attribute doit avoir :value éléments ou plus.',
+    ],
     'exists'               => 'L\'attribut :attribute est invalide.',
     'image'                => ':attribute doit être une image.',
     'image_extension'      => ':attribute doit avoir une extension d\'image valide et supportée.',
     'in'                   => 'L\'attribut :attribute est invalide.',
     'integer'              => ':attribute doit être un chiffre entier.',
     'ip'                   => ':attribute doit être une adresse IP valide.',
+    'ipv4'                 => ':attribute doit être une adresse IPv4 valide.',
+    'ipv6'                 => ':attribute doit être une adresse IPv6 valide.',
+    'json'                 => ':attribute doit être une chaine JSON valide.',
+    'lt'                   => [
+        'numeric' => ':attribute doit être plus petit que :value.',
+        'file'    => ':attribute doit être plus petit que :value kilobytes.',
+        'string'  => ':attribute doit être plus petit que :value caractères.',
+        'array'   => ':attribute doit avoir moins de :value éléments.',
+    ],
+    'lte'                  => [
+        'numeric' => ':attribute doit être plus petit ou égal à :value.',
+        'file'    => ':attribute doit être plus petit ou égal à :value kilobytes.',
+        'string'  => ':attribute doit être plus petit ou égal à :value caractères.',
+        'array'   => ':attribute ne doit pas avoir plus de :value éléments.',
+    ],
     'max'                  => [
         'numeric' => ':attribute ne doit pas excéder :max.',
         'file'    => ':attribute ne doit pas excéder :max kilobytes.',
@@ -57,6 +80,7 @@ return [
     ],
     'no_double_extension'  => ':attribute ne doit avoir qu\'une seule extension de fichier.',
     'not_in'               => 'L\'attribut sélectionné :attribute est invalide.',
+    'not_regex'            => ':attribute a un format invalide.',
     'numeric'              => ':attribute doit être un nombre.',
     'regex'                => ':attribute a un format invalide.',
     'required'             => ':attribute est un champ requis.',
@@ -78,34 +102,13 @@ return [
     'url'                  => ':attribute a un format invalide.',
     'uploaded'             => 'Le fichier n\'a pas pu être envoyé. Le serveur peut ne pas accepter des fichiers de cette taille.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => 'La confirmation du mot de passe est requise',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
index 1a7ba0b343de81336eb5a100d7f837754d9a49bc..b55add879c2dbdc677f5262d7daa59f9ccd07614 100644 (file)
@@ -21,7 +21,7 @@ return [
     'email' => 'Email',
     'password' => 'Jelszó',
     'password_confirm' => 'Jelszó megerősítése',
-    'password_hint' => 'Öt karakternél hosszabbnak kell lennie',
+    'password_hint' => 'Négy karakternél hosszabbnak kell lennie',
     'forgot_password' => 'Elfelejtett jelszó?',
     'remember_me' => 'Emlékezzen rám',
     'ldap_email_hint' => 'A fiókhoz használt email cím megadása.',
@@ -51,7 +51,7 @@ return [
 
 
     // Email Confirmation
-    'email_confirm_subject' => ':appName alklamazásban beállított email címet meg kell erősíteni',
+    'email_confirm_subject' => ':appName alkalmazásban beállított email címet meg kell erősíteni',
     'email_confirm_greeting' => ':appName köszöni a csatlakozást!',
     'email_confirm_text' => 'Az email címet a lenti gombra kattintva lehet megerősíteni:',
     'email_confirm_action' => 'Email megerősítése',
@@ -64,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Rá kell kattintani a regisztráció után nem sokkal elküldött emailben található hivatkozásra.',
     'email_not_confirmed_resend' => 'Ha nem érkezik meg a megerősítő email, a lenti űrlap beküldésével újra lehet küldeni.',
     'email_not_confirmed_resend_button' => 'Megerősítő email újraküldése',
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
 ];
\ No newline at end of file
index 4e72d5f94a15107e9372f719575f96459fca6417..4bf5b503036ff8ee69d3542806680297baf53624 100644 (file)
@@ -40,6 +40,10 @@ return [
     'add' => 'Hozzáadás',
 
     // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
     'sort_name' => 'Név',
     'sort_created_at' => 'Létrehozás dátuma',
     'sort_updated_at' => 'Frissítés dátuma',
@@ -55,8 +59,10 @@ return [
     'grid_view' => 'Rács nézet',
     'list_view' => 'Lista nézet',
     'default' => 'Alapértelmezés szerinti',
+    'breadcrumb' => 'Breadcrumb',
 
     // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => 'Profil megtekintése',
     'edit_profile' => 'Profil szerkesztése',
 
index 5bd865dd5378d9477f7c942f240c78b632264b4d..29f5822dc164ab5e3c681138cd9c0e531df4f915 100644 (file)
@@ -176,7 +176,7 @@ return [
     'pages_delete_confirm' => 'Biztosan törölhető ez az oldal?',
     'pages_delete_draft_confirm' => 'Biztosan törölhető ez a vázlatoldal?',
     'pages_editing_named' => ':pageName oldal szerkesztése',
-    'pages_edit_toggle_header' => 'Fejléc átkapcsolása',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'Vázlat mentése',
     'pages_edit_draft' => 'Oldal vázlat szerkesztése',
     'pages_editing_draft' => 'Vázlat szerkesztése',
@@ -234,6 +234,7 @@ return [
     ],
     'pages_draft_discarded' => 'Vázlat elvetve, a szerkesztő frissítve lesz az oldal aktuális tartalmával',
     'pages_specific' => 'Egy bizonyos oldal',
+    'pages_is_template' => 'Page Template',
 
     // Editor Sidebar
     'page_tags' => 'Oldal címkék',
@@ -242,9 +243,11 @@ return [
     'shelf_tags' => 'Polc címkék',
     'tag' => 'Címke',
     'tags' =>  'Címkék',
+    'tag_name' =>  'Tag Name',
     'tag_value' => 'Címke érték (nem kötelező)',
     'tags_explain' => "Címkék hozzáadása a tartalom jobb kategorizálásához.\nA mélyebb szervezettség megvalósításához hozzá lehet rendelni egy értéket a címkéhez.",
     'tags_add' => 'Másik címke hozzáadása',
+    'tags_remove' => 'Remove this tag',
     'attachments' => 'Csatolmányok',
     'attachments_explain' => 'Az oldalon megjelenő fájlok feltöltése vagy hivatkozások csatolása. Az oldal oldalsávjában fognak megjelenni.',
     'attachments_explain_instant_save' => 'Az itt történt módosítások azonnal el lesznek mentve.',
@@ -270,6 +273,12 @@ return [
     'attachments_file_uploaded' => 'Fájl sikeresen feltöltve',
     'attachments_file_updated' => 'Fájl sikeresen frissítve',
     'attachments_link_attached' => 'Hivatkozás sikeresen hozzácsatolva az oldalhoz',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
     // Profile View
     'profile_user_for_x' => 'Felhasználó ez óta: :time',
index 6791428044592f1524def4d941ffd7d395f07ea5..d2456a222119192511a2c1a364ac17025812adf4 100644 (file)
@@ -27,6 +27,7 @@ return [
     'social_account_register_instructions' => ':socialAccount beállítása használatával is lehet fiókot regisztrálni, ha még nem volt fiók létrehozva.',
     'social_driver_not_found' => 'Közösségi meghajtó nem található',
     'social_driver_not_configured' => ':socialAccount közösségi beállítások nem megfelelőek.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => ':filePath elérési út nem tölthető fel. Ellenőrizni kell, hogy az útvonal a kiszolgáló számára írható.',
index efebb4a10efe1b82f6ff9a49b3da68f1bc45a0cc..058784dc7e59d19f44a28231713e0025b56c0338 100644 (file)
@@ -29,6 +29,7 @@ return [
     'app_editor_desc' => 'Annak kiválasztása, hogy a felhasználók melyik szerkesztőt használhatják az oldalak szerkesztéséhez.',
     'app_custom_html' => 'Egyéni HTML fejléc tartalom',
     'app_custom_html_desc' => 'Az itt hozzáadott bármilyen tartalom be lesz illesztve minden oldal <head> szekciójának aljára. Ez hasznos a stílusok felülírásához van analitikai kódok hozzáadásához.',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'Alkalmazás logó',
     'app_logo_desc' => 'A képnek 43px magasnak kell lennie.<br>A nagy képek át lesznek méretezve.',
     'app_primary_color' => 'Alkalmazás elsődleges színe',
@@ -84,6 +85,7 @@ return [
     'role_manage_roles' => 'Szerepkörök és szerepkör engedélyek kezelése',
     'role_manage_entity_permissions' => 'Minden könyv, fejezet és oldalengedély kezelése',
     'role_manage_own_entity_permissions' => 'Saját könyv, fejezet és oldalak engedélyeinek kezelése',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'Alkalmazás beállításainak kezelése',
     'role_asset' => 'Eszköz jogosultságok',
     'role_asset_desc' => 'Ezek a jogosultság vezérlik a alapértelmezés szerinti hozzáférést a rendszerben található eszközökhöz. A könyvek, fejezetek és oldalak jogosultságai felülírják ezeket a jogosultságokat.',
@@ -108,6 +110,8 @@ return [
     'users_role_desc' => 'A felhasználó melyik szerepkörhöz lesz rendelve. Ha a felhasználó több szerepkörhöz van rendelve, akkor ezeknek a szerepköröknek a jogosultságai összeadódnak, és a a felhasználó a hozzárendelt szerepkörök minden képességét megkapja.',
     'users_password' => 'Felhasználó jelszava',
     'users_password_desc' => 'Az alkalmazásba bejelentkezéshez használható jelszó beállítása. Legalább 5 karakter hosszúnak kell lennie.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => 'Külső hitelesítés azonosítója',
     'users_external_auth_id_desc' => 'Ez az azonosító lesz használva a felhasználó ellenőrzéséhez mikor az LDAP rendszerrel kommunikál.',
     'users_password_warning' => 'A lenti mezőket csak a jelszó módosításához kell kitölteni.',
@@ -131,9 +135,8 @@ return [
     'users_social_connected' => ':socialAccount fiók sikeresen csatlakoztatva a profilhoz.',
     'users_social_disconnected' => ':socialAccount fiók sikeresen lecsatlakoztatva a profilról.',
 
-    //! Since these labels are already localized this array does not need to be
-    //! translated in the language-specific files.
-    //! DELETE BELOW IF COPIED FROM EN
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
     //!////////////////////////////////
     'language_select' => [
         'en' => 'English',
@@ -148,14 +151,16 @@ return [
         'sk' => 'Slovensky',
         'cs' => 'Česky',
         'sv' => 'Svenska',
-        'kr' => '한국어',
+        'ko' => '한국어',
         'ja' => '日本語',
         'pl' => 'Polski',
         'it' => 'Italian',
         'ru' => 'Русский',
         'uk' => 'Українська',
         'zh_CN' => '简体中文',
-        'zh_TW' => '繁體中文'
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
     ]
     //!////////////////////////////////
 ];
index 68a4446437068fe6bc9745a48bead4b02659d8db..023f9f0a4bcb6385d57ef6074c801f46b5cf6c06 100644 (file)
@@ -30,13 +30,41 @@ return [
     'digits'               => ':attribute :digits számból kell álljon.',
     'digits_between'       => ':attribute hosszának :min és :max számjegy között kell lennie.',
     'email'                => ':attribute érvényes email cím kell legyen.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => ':attribute mező kötelező.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => 'A kiválasztott :attribute érvénytelen.',
     'image'                => ':attribute kép kell legyen.',
     'image_extension'      => 'A :attribute kép kiterjesztése érvényes és támogatott kell legyen.',
     'in'                   => 'A kiválasztott :attribute érvénytelen.',
     'integer'              => ':attribute egész szám kell legyen.',
     'ip'                   => ':attribute érvényes IP cím kell legyen.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => ':attribute nem lehet nagyobb mint :max.',
         'file'    => ':attribute nem lehet nagyobb mint :max kilobájt.',
@@ -52,6 +80,7 @@ return [
     ],
     'no_double_extension'  => ':attribute csak egy fájlkiterjesztéssel rendelkezhet.',
     'not_in'               => 'A kiválasztott :attribute érvénytelen.',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attribute szám kell legyen.',
     'regex'                => ':attribute formátuma érvénytelen.',
     'required'             => ':attribute mező kötelező.',
index 8d7b4bcfe92881a81a3ca47bfd041c6673500cb6..c66651489394fc5b0ea34c8eda4d37729a93583c 100755 (executable)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => 'ha creato la pagina',
     'page_create_notification'    => 'Pagina Creata Correttamente',
@@ -25,7 +23,7 @@ return [
     'chapter_update_notification' => 'Capitolo Aggiornato Correttamente',
     'chapter_delete'              => 'ha eliminato il capitolo',
     'chapter_delete_notification' => 'Capitolo Eliminato Correttamente',
-    'chapter_move'                => 'ha mosso il capitolo',
+    'chapter_move'                => 'ha spostato il capitolo',
 
     // Books
     'book_create'                 => 'ha creato il libro',
@@ -37,6 +35,14 @@ return [
     'book_sort'                   => 'ha ordinato il libro',
     'book_sort_notification'      => 'Libro Riordinato Correttamente',
 
+    // Bookshelves
+    'bookshelf_create'            => 'ha creato la Libreria',
+    'bookshelf_create_notification'    => 'Libreria Creata Correttamente',
+    'bookshelf_update'                 => 'ha aggiornato la libreria',
+    'bookshelf_update_notification'    => 'Libreria Aggiornata Correttamente',
+    'bookshelf_delete'                 => 'ha eliminato la libreria',
+    'bookshelf_delete_notification'    => 'Libreria Eliminata Correttamente',
+
     // Other
     'commented_on'                => 'ha commentato in',
 ];
index 68fee41a56496436aeadbf0a4fba8cab4dd779b0..234af2eeba723d1839cc3c94ff44f034b2c2ccab 100755 (executable)
@@ -1,21 +1,15 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => 'Credenziali errate.',
     'throttle' => 'Troppi tentativi di login. Riprova in :seconds secondi.',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => 'Registrati',
     'log_in' => 'Login',
     'log_in_with' => 'Login con :socialDriver',
@@ -27,11 +21,13 @@ return [
     'email' => 'Email',
     'password' => 'Password',
     'password_confirm' => 'Conferma Password',
-    'password_hint' => 'Deve essere più di 5 caratteri',
+    'password_hint' => 'Deve essere più di 7 caratteri',
     'forgot_password' => 'Password dimenticata?',
     'remember_me' => 'Ricordami',
     'ldap_email_hint' => 'Inserisci un email per usare quest\'account.',
     'create_account' => 'Crea Account',
+    'already_have_account' => 'Hai già un account?',
+    'dont_have_account' => 'Non hai un account?',
     'social_login' => 'Login Social',
     'social_registration' => 'Registrazione Social',
     'social_registration_text' => 'Registrati usando un altro servizio.',
@@ -43,23 +39,18 @@ return [
     'register_success' => 'Grazie per la registrazione! Sei registrato e loggato.',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => 'Reimposta Password',
     'reset_password_send_instructions' => 'Inserisci il tuo indirizzo sotto e ti verrà inviata una mail contenente un link per resettare la tua password.',
     'reset_password_send_button' => 'Invia Link Reset',
     'reset_password_sent_success' => 'Un link di reset è stato mandato a :email.',
     'reset_password_success' => 'La tua password è stata resettata correttamente.',
-
     'email_reset_subject' => 'Reimposta la password di :appName',
     'email_reset_text' => 'Stai ricevendo questa mail perché abbiamo ricevuto una richiesta di reset della password per il tuo account.',
     'email_reset_not_requested' => 'Se non hai richiesto un reset della password, ignora questa mail.',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => 'Conferma email per :appName',
     'email_confirm_greeting' => 'Grazie per esserti registrato a :appName!',
     'email_confirm_text' => 'Conferma il tuo indirizzo email cliccando il pulsante sotto:',
@@ -73,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Clicca il link nella mail mandata subito dopo la tua registrazione.',
     'email_not_confirmed_resend' => 'Se non riesci a trovare la mail puoi rimandarla cliccando il pulsante sotto.',
     'email_not_confirmed_resend_button' => 'Reinvia Conferma',
+
+    // User Invite
+    'user_invite_email_subject' => 'Sei stato invitato a unirti a :appName!',
+    'user_invite_email_greeting' => 'Un account è stato creato per te su :appName.',
+    'user_invite_email_text' => 'Clicca sul pulsante qui sotto per impostare una password e ottenere l\'accesso:',
+    'user_invite_email_action' => 'Imposta Password',
+    'user_invite_page_welcome' => 'Benvenuto in :appName!',
+    'user_invite_page_text' => 'Per completare il tuo account e ottenere l\'accesso devi impostare una password che verrà utilizzata per accedere a :appName in futuro.',
+    'user_invite_page_confirm_button' => 'Conferma Password',
+    'user_invite_success' => 'Password impostata, ora hai accesso a :appName!'
 ];
\ No newline at end of file
index bace09621b48bd65be443cbe1a45293a28c6d66f..1873a100c6dfda4ec9dc0c48144948af009e9fbd 100755 (executable)
@@ -1,47 +1,54 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => 'Annulla',
     'confirm' => 'Conferma',
     'back' => 'Indietro',
     'save' => 'Salva',
     'continue' => 'Continua',
     'select' => 'Seleziona',
-    'more' => 'More',
+    'toggle_all' => 'Attiva/disattiva tutto',
+    'more' => 'Altro',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => 'Nome',
     'description' => 'Descrizione',
     'role' => 'Ruolo',
     'cover_image' => 'Immagine di copertina',
     'cover_image_description' => 'Questa immagine dovrebbe essere approssimatamente 440x250px.',
-
-    /**
-     * Actions
-     */
+    
+    // Actions
     'actions' => 'Azioni',
     'view' => 'Visualizza',
+    'view_all' => 'Vedi tutto',
     'create' => 'Crea',
     'update' => 'Aggiorna',
     'edit' => 'Modifica',
     'sort' => 'Ordina',
     'move' => 'Muovi',
+    'copy' => 'Copia',
     'reply' => 'Rispondi',
     'delete' => 'Elimina',
     'search' => 'Cerca',
     'search_clear' => 'Pulisci Ricerca',
-    'reset' => 'Reset',
+    'reset' => 'Azzera',
     'remove' => 'Rimuovi',
     'add' => 'Aggiungi',
 
-    /**
-     * Misc
-     */
+    // Sort Options
+    'sort_options' => 'Opzioni Ordinamento',
+    'sort_direction_toggle' => 'Inverti Direzione Ordinamento',
+    'sort_ascending' => 'Ordine Ascendente',
+    'sort_descending' => 'Ordine Discendente',
+    'sort_name' => 'Nome',
+    'sort_created_at' => 'Data Creazione',
+    'sort_updated_at' => 'Data Aggiornamento',
+
+    // Misc
     'deleted_user' => 'Utente Eliminato',
     'no_activity' => 'Nessuna attività da mostrare',
     'no_items' => 'Nessun elemento disponibile',
@@ -51,16 +58,19 @@ return [
     'details' => 'Dettagli',
     'grid_view' => 'Visualizzazione Griglia',
     'list_view' => 'Visualizzazione Lista',
+    'default' => 'Predefinito',
+    'breadcrumb' => 'Navigazione',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Menu del profilo',
     'view_profile' => 'Visualizza Profilo',
     'edit_profile' => 'Modifica Profilo',
 
-    /**
-     * Email Content
-     */
+    // Layout tabs
+    'tab_info' => 'Info',
+    'tab_content' => 'Contenuto',
+
+    // Email Content
     'email_action_help' => 'Se hai problemi nel cliccare il pulsante ":actionText", copia e incolla lo URL sotto nel tuo browser:',
     'email_rights' => 'Tutti i diritti riservati',
-];
\ No newline at end of file
+];
index c9ab18a3ecfbcdf979958480ebfde43f86a2f0ee..360409646e3040033dd88acad388bd8a5870604d 100755 (executable)
@@ -1,16 +1,17 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => 'Selezione Immagine',
     'image_all' => 'Tutte',
     'image_all_title' => 'Visualizza tutte le immagini',
     'image_book_title' => 'Visualizza immagini caricate in questo libro',
     'image_page_title' => 'Visualizza immagini caricate in questa pagina',
     'image_search_hint' => 'Cerca immagine per nome',
-    'image_uploaded' => 'Uploaded :uploadedDate',
+    'image_uploaded' => 'Caricato :uploadedDate',
     'image_load_more' => 'Carica Altre',
     'image_image_name' => 'Nome Immagine',
     'image_delete_used' => 'Questa immagine è usata nelle pagine elencate.',
@@ -22,12 +23,11 @@ return [
     'image_upload_success' => 'Immagine caricata correttamente',
     'image_update_success' => 'Dettagli immagine aggiornati correttamente',
     'image_delete_success' => 'Immagine eliminata correttamente',
+    'image_upload_remove' => 'Rimuovi',
 
-    /**
-     * Code editor
-     */
+    // Code Editor
     'code_editor' => 'Modifica Codice',
     'code_language' => 'Linguaggio Codice',
     'code_content' => 'Contenuto Codice',
     'code_save' => 'Salva Codice',
-];
\ No newline at end of file
+];
index ad1733b9161f7262cb6a453880a6517586e49ff2..89beb9af580f6bb97a70475a0b956111f2e6acf1 100755 (executable)
@@ -1,14 +1,17 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => 'Creati di recente',
     'recently_created_pages' => 'Pagine create di recente',
     'recently_updated_pages' => 'Pagine aggiornate di recente',
     'recently_created_chapters' => 'Capitoli creati di recente',
     'recently_created_books' => 'Libri creati di recente',
+    'recently_created_shelves' => 'Librerie Create Di Recente',
     'recently_update' => 'Aggiornati di recente',
     'recently_viewed' => 'Visti di recente',
     'recent_activity' => 'Attività Recente',
@@ -31,17 +34,13 @@ return [
     'export_pdf' => 'File PDF',
     'export_text' => 'File di testo',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => 'Permessi',
     'permissions_intro' => 'Una volta abilitati, questi permessi avranno la priorità su tutti gli altri.',
     'permissions_enable' => 'Abilita Permessi Custom',
     'permissions_save' => 'Salva Permessi',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => 'Risultati Ricerca',
     'search_total_results_found' => ':count risultato trovato|:count risultati trovati',
     'search_clear' => 'Pulisci Ricerca',
@@ -52,11 +51,13 @@ return [
     'search_content_type' => 'Tipo di Contenuto',
     'search_exact_matches' => 'Corrispondenza Esatta',
     'search_tags' => 'Ricerche Tag',
+    'search_options' => 'Opzioni',
     'search_viewed_by_me' => 'Visti',
     'search_not_viewed_by_me' => 'Non visti',
     'search_permissions_set' => 'Permessi impostati',
     'search_created_by_me' => 'Creati da me',
     'search_updated_by_me' => 'Aggiornati da me',
+    'search_date_options' => 'Opzioni Data',
     'search_updated_before' => 'Aggiornati prima del',
     'search_updated_after' => 'Aggiornati dopo il',
     'search_created_before' => 'Creati prima del',
@@ -64,9 +65,39 @@ return [
     'search_set_date' => 'Imposta Data',
     'search_update' => 'Aggiorna Ricerca',
 
-    /**
-     * Books
-     */
+    // Shelves
+    'shelf' => 'Libreria',
+    'shelves' => 'Librerie',
+    'x_shelves' => ':count Libreria|:count Librerie',
+    'shelves_long' => 'Librerie',
+    'shelves_empty' => 'Nessuna libreria è stata creata',
+    'shelves_create' => 'Crea Nuova Libreria',
+    'shelves_popular' => 'Librerie Popolari',
+    'shelves_new' => 'Nuove Librerie',
+    'shelves_new_action' => 'Nuova Libreria',
+    'shelves_popular_empty' => 'Le librerie più popolari appariranno qui.',
+    'shelves_new_empty' => 'Le librerie create più di recente appariranno qui.',
+    'shelves_save' => 'Salva Libreria',
+    'shelves_books' => 'Libri in questa libreria',
+    'shelves_add_books' => 'Aggiungi libri a questa libreria',
+    'shelves_drag_books' => 'Trascina i libri qui per aggiungerli a questa libreria',
+    'shelves_empty_contents' => 'Questa libreria non ha libri assegnati',
+    'shelves_edit_and_assign' => 'Modifica la libreria per assegnare i libri',
+    'shelves_edit_named' => 'Modifica Libreria :name',
+    'shelves_edit' => 'Modifica Libreria',
+    'shelves_delete' => 'Elimina Libreria',
+    'shelves_delete_named' => 'Elimina Libreria :name',
+    'shelves_delete_explain' => "La libreria ':name' verrà eliminata. I libri contenuti non verranno eliminati.",
+    'shelves_delete_confirmation' => 'Sei sicuro di voler eliminare questa libreria?',
+    'shelves_permissions' => 'Permessi Libreria',
+    'shelves_permissions_updated' => 'Permessi Libreria Aggiornati',
+    'shelves_permissions_active' => 'Permessi Attivi Libreria',
+    'shelves_copy_permissions_to_books' => 'Copia Permessi ai Libri',
+    'shelves_copy_permissions' => 'Copia Permessi',
+    'shelves_copy_permissions_explain' => 'Verranno applicati tutti i permessi della libreria ai libri contenuti. Prima di attivarlo, assicurati che ogni permesso di questa libreria sia salvato.',
+    'shelves_copy_permission_success' => 'Permessi della libreria copiati in :count books',
+
+    // Books
     'book' => 'Libro',
     'books' => 'Libri',
     'x_books' => ':count Libro|:count Libri',
@@ -74,6 +105,7 @@ return [
     'books_popular' => 'Libri Popolari',
     'books_recent' => 'Libri Recenti',
     'books_new' => 'Nuovi Libri',
+    'books_new_action' => 'Nuovo Libro',
     'books_popular_empty' => 'I libri più popolari appariranno qui.',
     'books_new_empty' => 'I libri creati più di recente appariranno qui.',
     'books_create' => 'Crea Nuovo Libro',
@@ -89,7 +121,6 @@ return [
     'books_permissions_updated' => 'Permessi del libro aggiornati',
     'books_empty_contents' => 'Non ci sono pagine o capitoli per questo libro.',
     'books_empty_create_page' => 'Crea una nuova pagina',
-    'books_empty_or' => 'o',
     'books_empty_sort_current_book' => 'Ordina il libro corrente',
     'books_empty_add_chapter' => 'Aggiungi un capitolo',
     'books_permissions_active' => 'Permessi libro attivi',
@@ -97,12 +128,15 @@ return [
     'books_navigation' => 'Navigazione Libro',
     'books_sort' => 'Ordina il contenuto del libro',
     'books_sort_named' => 'Ordina il libro :bookName',
+    'books_sort_name' => 'Ordina per Nome',
+    'books_sort_created' => 'Ordina per Data di Creazione',
+    'books_sort_updated' => 'Ordina per Data di Aggiornamento',
+    'books_sort_chapters_first' => 'Capitoli Per Primi',
+    'books_sort_chapters_last' => 'Capitoli Per Ultimi',
     'books_sort_show_other' => 'Mostra Altri Libri',
     'books_sort_save' => 'Salva il nuovo ordine',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => 'Capitolo',
     'chapters' => 'Capitoli',
     'x_chapters' => ':count Capitolo|:count Capitoli',
@@ -125,9 +159,7 @@ return [
     'chapters_permissions_success' => 'Permessi Capitolo Aggiornati',
     'chapters_search_this' => 'Cerca in questo capitolo',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => 'Pagina',
     'pages' => 'Pagine',
     'x_pages' => ':count Pagina|:count Pagine',
@@ -144,7 +176,7 @@ return [
     'pages_delete_confirm' => 'Sei sicuro di voler eliminare questa pagina?',
     'pages_delete_draft_confirm' => 'Sei sicuro di voler eliminare la bozza di questa pagina?',
     'pages_editing_named' => 'Modifica :pageName',
-    'pages_edit_toggle_header' => 'Mostra/Nascondi header',
+    'pages_edit_draft_options' => 'Opzioni Bozza',
     'pages_edit_save_draft' => 'Salva Bozza',
     'pages_edit_draft' => 'Modifica Bozza della pagina',
     'pages_editing_draft' => 'Modifica Bozza',
@@ -166,6 +198,9 @@ return [
     'pages_not_in_chapter' => 'La pagina non è in un capitolo',
     'pages_move' => 'Muovi Pagina',
     'pages_move_success' => 'Pagina mossa in ":parentName"',
+    'pages_copy' => 'Copia Pagina',
+    'pages_copy_desination' => 'Copia Destinazione',
+    'pages_copy_success' => 'Pagina copiata correttamente',
     'pages_permissions' => 'Permessi Pagina',
     'pages_permissions_success' => 'Permessi pagina aggiornati',
     'pages_revision' => 'Versione',
@@ -175,7 +210,9 @@ return [
     'pages_revisions_created_by' => 'Creata Da',
     'pages_revisions_date' => 'Data Versione',
     'pages_revisions_number' => '#',
-    'pages_revisions_changelog' => 'Changelog',
+    'pages_revisions_numbered' => 'Revisione #:id',
+    'pages_revisions_numbered_changes' => 'Modifiche Revisione #:id',
+    'pages_revisions_changelog' => 'Cambiamenti',
     'pages_revisions_changes' => 'Cambiamenti',
     'pages_revisions_current' => 'Versione Corrente',
     'pages_revisions_preview' => 'Anteprima',
@@ -195,17 +232,22 @@ return [
         'time_b' => 'negli ultimi :minCount minuti',
         'message' => ':start :time. Assicurati di non sovrascrivere le modifiche degli altri!',
     ],
-    'pages_draft_discarded' => "Bozza scartata, l'editor è stato aggiornato con il contenuto corrente della pagina",
+    'pages_draft_discarded' => 'Bozza scartata, l\'editor è stato aggiornato con il contenuto corrente della pagina',
+    'pages_specific' => 'Pagina Specifica',
+    'pages_is_template' => 'Template Pagina',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => 'Tag Pagina',
+    'chapter_tags' => 'Tag Capitolo',
+    'book_tags' => 'Tag Libro',
+    'shelf_tags' => 'Tag Libreria',
     'tag' => 'Tag',
-    'tags' =>  '',
+    'tags' =>  'Tag',
+    'tag_name' =>  'Nome Tag',
     'tag_value' => 'Valore (Opzionale)',
     'tags_explain' => "Aggiungi tag per categorizzare meglio il contenuto. \n Puoi assegnare un valore ai tag per una migliore organizzazione.",
     'tags_add' => 'Aggiungi un altro tag',
+    'tags_remove' => 'Rimuovi questo tag',
     'attachments' => 'Allegati',
     'attachments_explain' => 'Carica alcuni file o allega link per visualizzarli nella pagina. Questi sono visibili nella sidebar della pagina.',
     'attachments_explain_instant_save' => 'I cambiamenti qui sono salvati istantaneamente.',
@@ -213,7 +255,7 @@ return [
     'attachments_upload' => 'Carica File',
     'attachments_link' => 'Allega Link',
     'attachments_set_link' => 'Imposta Link',
-    'attachments_delete_confirm' => "Clicca elimina nuovamente per confermare l'eliminazione di questo allegato.",
+    'attachments_delete_confirm' => 'Clicca elimina nuovamente per confermare l\'eliminazione di questo allegato.',
     'attachments_dropzone' => 'Rilascia file o clicca qui per allegare un file',
     'attachments_no_files' => 'Nessun file è stato caricato',
     'attachments_explain_link' => 'Puoi allegare un link se preferisci non caricare un file. Questo può essere un link a un\'altra pagina o a un file in un cloud.',
@@ -231,19 +273,22 @@ return [
     'attachments_file_uploaded' => 'File caricato correttamente',
     'attachments_file_updated' => 'File aggiornato correttamente',
     'attachments_link_attached' => 'Link allegato correttamente alla pagina',
+    'templates' => 'Template',
+    'templates_set_as_template' => 'La pagina è un template',
+    'templates_explain_set_as_template' => 'Puoi impostare questa pagina come template in modo che il suo contenuto sia utilizzato quando si creano altre pagine. Gli altri utenti potranno utilizzare questo template se avranno i permessi di visualizzazione per questa pagina.',
+    'templates_replace_content' => 'Rimpiazza contenuto della pagina',
+    'templates_append_content' => 'Appendi al contenuto della pagina',
+    'templates_prepend_content' => 'Prependi al contenuto della pagina',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => 'Utente da :time',
     'profile_created_content' => 'Contenuti Creati',
     'profile_not_created_pages' => ':userName non ha creato pagine',
     'profile_not_created_chapters' => ':userName non ha creato capitoli',
     'profile_not_created_books' => ':userName non ha creato libri',
+    'profile_not_created_shelves' => ':userName non ha creato alcuna libreria',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => 'Commento',
     'comments' => 'Commenti',
     'comment_add' => 'Aggiungi Commento',
@@ -261,10 +306,9 @@ return [
     'comment_delete_confirm' => 'Sei sicuro di voler elminare questo commento?',
     'comment_in_reply_to' => 'In risposta a :commentId',
 
-     /**
-     * Revision
-     */
+    // Revision
     'revision_delete_confirm' => 'Sei sicuro di voler eliminare questa revisione?',
+    'revision_restore_confirm' => 'Sei sicuro di voler ripristinare questa revisione? Il contenuto della pagina verrà rimpiazzato.',
     'revision_delete_success' => 'Revisione cancellata',
     'revision_cannot_delete_latest' => 'Impossibile eliminare l\'ultima revisione.'
 ];
\ No newline at end of file
index 17be482c72748c66c401fa8c15fe3560e9f9e440..466aabcd5fb55f103d63214a25a85b671e201d59 100755 (executable)
@@ -1,14 +1,12 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => 'Non hai il permesso di accedere alla pagina richiesta.',
-    'permissionJson' => "Non hai il permesso di eseguire l'azione richiesta.",
+    'permissionJson' => 'Non hai il permesso di eseguire l\'azione richiesta.',
 
     // Auth
     'error_user_exists_different_creds' => 'Un utente con la mail :email esiste già ma con credenziali differenti.',
@@ -29,18 +27,20 @@ return [
     'social_account_register_instructions' => 'Se non hai ancora un account, puoi registrarti usando l\'opzione :socialAccount.',
     'social_driver_not_found' => 'Driver social non trovato',
     'social_driver_not_configured' => 'Le impostazioni di :socialAccount non sono configurate correttamente.',
+    'invite_token_expired' => 'Il link di invito è scaduto. Puoi provare a resettare la password del tuo account.',
 
     // System
     'path_not_writable' => 'La path :filePath non può essere scritta. Controlla che abbia i permessi corretti.',
     'cannot_get_image_from_url' => 'Impossibile scaricare immagine da :url',
     'cannot_create_thumbs' => 'Il server non può creare thumbnail. Controlla che l\'estensione GD sia installata.',
     'server_upload_limit' => 'Il server non permette un upload di questa grandezza. Prova con un file più piccolo.',
+    'uploaded'  => 'Il server non consente upload di questa grandezza. Prova un file più piccolo.',
     'image_upload_error' => 'C\'è stato un errore caricando l\'immagine',
     'image_upload_type_error' => 'Il tipo di immagine in upload non è valido',
     'file_upload_timeout' => 'Il caricamento del file è scaduto.',
 
     // Attachments
-    'attachment_page_mismatch' => 'Page mismatch during attachment update',
+    'attachment_page_mismatch' => 'La pagina non è corrisposta durante l\'aggiornamento dell\'allegato',
     'attachment_not_found' => 'Allegato non trovato',
 
     // Pages
@@ -49,6 +49,7 @@ return [
 
     // Entities
     'entity_not_found' => 'Entità non trovata',
+    'bookshelf_not_found' => 'Libreria non trovata',
     'book_not_found' => 'Libro non trovato',
     'page_not_found' => 'Pagina non trovata',
     'chapter_not_found' => 'Capitolo non trovato',
@@ -64,6 +65,7 @@ return [
     'role_cannot_be_edited' => 'Questo ruolo non può essere modificato',
     'role_system_cannot_be_deleted' => 'Questo ruolo è di sistema e non può essere eliminato',
     'role_registration_default_cannot_delete' => 'Questo ruolo non può essere eliminato finchè è impostato come default alla registrazione',
+    'role_cannot_remove_only_admin' => 'Questo utente è l\'unico con assegnato il ruolo di amministratore. Assegna il ruolo di amministratore ad un altro utente prima di rimuoverlo qui.',
 
     // Comments
     'comment_list' => 'C\'è stato un errore scaricando i commenti.',
@@ -79,4 +81,5 @@ return [
     'error_occurred' => 'C\'è Stato un errore',
     'app_down' => ':appName è offline',
     'back_soon' => 'Ritornerà presto.',
-];
\ No newline at end of file
+
+];
index e3c561423781ccba040e6ecc4f01da86a0db0bb2..1fba272fdeb30ee9512723af0b18540dec04e5d7 100755 (executable)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; Precedente',
     'next'     => 'Successivo &raquo;',
 
index a584aa33355297737cde65334de6378a39a0fdcd..0f95a3e0672ee0be86bfa1d447db9559d93007c9 100755 (executable)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => 'La password deve avere almeno sei caratteri e corrispondere alla conferma.',
     'user' => "Non possiamo trovare un utente per quella mail.",
     'token' => 'Questo token per reimpostare la password non è valido.',
index d14dbfb728b22e6916ad3cece042ad40e39fdf64..fe312796525388a826e4ba818cc2d62fe13d025f 100755 (executable)
@@ -1,59 +1,70 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
-
+    // Common Messages
     'settings' => 'Impostazioni',
     'settings_save' => 'Salva Impostazioni',
     'settings_save_success' => 'Impostazioni salvate',
 
-    /**
-     * App settings
-     */
-
-    'app_settings' => 'Impostazioni App',
+    // App Settings
+    'app_customization' => 'Personalizzazione',
+    'app_features_security' => 'Funzioni & Sicurezza',
     'app_name' => 'Nome applicazione',
     'app_name_desc' => 'Questo nome è mostrato nell\'header e in tutte le mail.',
     'app_name_header' => 'Mostrare il nome nell\'header',
+    'app_public_access' => 'Accesso Pubblico',
+    'app_public_access_desc' => 'Abilitando questa opzione, i visitatori, che non sono loggati, potranno accedere ai contenuti nella tua istanza BookStack.',
+    'app_public_access_desc_guest' => 'L\'accesso ai visitatori pubblici può essere controllato attraverso l\'utente "Guest".',
+    'app_public_access_toggle' => 'Permetti accesso pubblico',
     'app_public_viewing' => 'Consentire la visione pubblica?',
     'app_secure_images' => 'Abilitare una sicurezza maggiore per le immagini caricate?',
+    'app_secure_images_toggle' => 'Abilita sicurezza aggiuntiva negli upload delle immagini',
     'app_secure_images_desc' => 'Per una ragione di prestazioni, tutte le immagini sono pubbliche. Questa opzione aaggiunge una stringa, difficile da indovinare, random negli url delle immagini. Assicurati che il listing delle cartelle non sia abilitato per prevenire un accesso semplice.',
     'app_editor' => 'Editor pagine',
     'app_editor_desc' => 'Seleziona quale editor verrà usato da tutti gli utenti per modificare le pagine.',
     'app_custom_html' => 'Contenuto Head HTML Custom',
     'app_custom_html_desc' => 'Qualsiasi contenuto aggiunto qui verrà inserito alla fine della sezione <head> di tutte le pagine. Questo è utile per sovrascrivere lo stile o aggiungere il codice per gli analytics.',
+    'app_custom_html_disabled_notice' => 'Il contenuto HTML personalizzato è disabilitato su questa pagina impostazioni per garantire che eventuali modifiche possano essere ripristinate.',
     'app_logo' => 'Logo applicazione',
     'app_logo_desc' => 'Questa immagine dovrebbe essere 43px in altezza. <br>Immagini più grandi verranno scalate.',
     'app_primary_color' => 'Colore primario applicazione',
     'app_primary_color_desc' => 'Deve essere un valore hex. <br>Lascia vuoto per reimpostare il colore di default.',
     'app_homepage' => 'Homepage Applicazione',
     'app_homepage_desc' => 'Seleziona una pagina da mostrare nella home anzichè quella di default. I permessi della pagina sono ignorati per quella selezionata.',
-    'app_homepage_default' => 'Homepage di default scelta',
+    'app_homepage_select' => 'Seleziona una pagina',
     'app_disable_comments' => 'Disattiva commenti',
+    'app_disable_comments_toggle' => 'Disabilita commenti',
     'app_disable_comments_desc' => 'Disabilita i commenti su tutte le pagine nell\'applicazione. I commenti esistenti non sono mostrati. ',
 
-    /**
-     * Registration settings
-     */
-
+    // Registration Settings
     'reg_settings' => 'Impostazioni Registrazione',
-    'reg_allow' => 'Consentire Registrazione?',
+    'reg_enable' => 'Abilita Registrazione',
+    'reg_enable_toggle' => 'Abilita registrazione',
+    'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
     'reg_default_role' => 'Ruolo predefinito dopo la registrazione',
-    'reg_confirm_email' => 'Richiedere la conferma della mail?',
+    'reg_email_confirmation' => 'Conferma Email',
+    'reg_email_confirmation_toggle' => 'Richiedi conferma email',
     'reg_confirm_email_desc' => 'Se la restrizione per dominio è usata la conferma della mail sarà richiesta e la scelta ignorata.',
     'reg_confirm_restrict_domain' => 'Restringi la registrazione al dominio',
-    'reg_confirm_restrict_domain_desc' => "Inserisci una lista separata da virgola di domini di email a cui vorresti restringere la registrazione. Agli utenti verrà inviata una mail per confermare il loro indirizzo prima che possano interagire con l'applicazione. <br> Nota che gli utenti saranno in grado di cambiare il loro indirizzo dopo aver completato la registrazione.",
+    'reg_confirm_restrict_domain_desc' => 'Inserisci una lista separata da virgola di domini di email a cui vorresti restringere la registrazione. Agli utenti verrà inviata una mail per confermare il loro indirizzo prima che possano interagire con l\'applicazione. <br> Nota che gli utenti saranno in grado di cambiare il loro indirizzo dopo aver completato la registrazione.',
     'reg_confirm_restrict_domain_placeholder' => 'Nessuna restrizione impostata',
 
-    /**
-     * Role settings
-     */
+    // Maintenance settings
+    'maint' => 'Manutenzione',
+    'maint_image_cleanup' => 'Pulizia Immagini',
+    'maint_image_cleanup_desc' => "Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.",
+    'maint_image_cleanup_ignore_revisions' => 'Ignora le immagini nelle revisioni',
+    'maint_image_cleanup_run' => 'Esegui Pulizia',
+    'maint_image_cleanup_warning' => ':count immagini potenzialmente inutilizzate sono state trovate. Sei sicuro di voler eliminare queste immagini?',
+    'maint_image_cleanup_success' => ':count immagini potenzialmente inutilizzate trovate e eliminate!',
+    'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!',
 
+    // Role Settings
     'roles' => 'Ruoli',
     'role_user_roles' => 'Ruoli Utente',
     'role_create' => 'Crea Nuovo Ruolo',
@@ -68,36 +79,45 @@ return [
     'role_details' => 'Dettagli Ruolo',
     'role_name' => 'Nome Ruolo',
     'role_desc' => 'Breve Descrizione del Ruolo',
+    'role_external_auth_id' => 'ID Autenticazione Esterna',
     'role_system' => 'Permessi di Sistema',
     'role_manage_users' => 'Gestire gli utenti',
     'role_manage_roles' => 'Gestire ruoli e permessi di essi',
     'role_manage_entity_permissions' => 'Gestire tutti i permessi di libri, capitoli e pagine',
     'role_manage_own_entity_permissions' => 'Gestire i permessi sui propri libri, capitoli e pagine',
+    'role_manage_page_templates' => 'Gestisci template pagine',
     'role_manage_settings' => 'Gestire impostazioni app',
     'role_asset' => 'Permessi Entità',
-    'role_asset_desc' => "Questi permessi controllano l'accesso di default alle entità. I permessi nei Libri, Capitoli e Pagine sovrascriveranno questi.",
+    'role_asset_desc' => 'Questi permessi controllano l\'accesso di default alle entità. I permessi nei Libri, Capitoli e Pagine sovrascriveranno questi.',
+    'role_asset_admins' => 'Gli amministratori hanno automaticamente accesso a tutti i contenuti ma queste opzioni possono mostrare o nascondere le opzioni della UI.',
     'role_all' => 'Tutti',
     'role_own' => 'Propri',
-    'role_controlled_by_asset' => "Controllato dall'entità in cui sono caricati",
+    'role_controlled_by_asset' => 'Controllato dall\'entità in cui sono caricati',
     'role_save' => 'Salva Ruolo',
     'role_update_success' => 'Ruolo aggiornato correttamente',
     'role_users' => 'Utenti in questo ruolo',
     'role_users_none' => 'Nessun utente assegnato a questo ruolo',
 
-    /**
-     * Users
-     */
-
+    // Users
     'users' => 'Utenti',
     'user_profile' => 'Profilo Utente',
     'users_add_new' => 'Aggiungi Nuovo Utente',
     'users_search' => 'Cerca Utenti',
+    'users_details' => 'Dettagli Utente',
+    'users_details_desc' => 'Imposta un nome e un indirizzo email per questo utente. L\'indirizzo email verrà utilizzato per accedere all\'applicazione.',
+    'users_details_desc_no_email' => 'Imposta un nome per questo utente così gli altri possono riconoscerlo.',
     'users_role' => 'Ruoli Utente',
+    'users_role_desc' => 'Seleziona a quali ruoli verrà assegnato questo utente. Se un utente è assegnato a più ruoli riceverà tutte le abilità dei ruoli assegnati.',
+    'users_password' => 'Password Utente',
+    'users_password_desc' => 'Imposta una password utilizzata per accedere all\'applicazione. Deve essere lunga almeno 6 caratteri.',
+    'users_send_invite_text' => 'Puoi scegliere di inviare a questo utente un\'email di invito che permette loro di impostare la propria password altrimenti puoi impostare la password tu stesso.',
+    'users_send_invite_option' => 'Invia email di invito',
     'users_external_auth_id' => 'ID Autenticazioni Esterna',
+    'users_external_auth_id_desc' => 'Questo è l\'ID utilizzato per abbinare questo utente quando si comunica con il sistema LDAP.',
     'users_password_warning' => 'Riempi solo se desideri cambiare la tua password:',
     'users_system_public' => 'Questo utente rappresente qualsiasi ospite che visita il sito. Non può essere usato per effettuare il login ma è assegnato automaticamente.',
     'users_delete' => 'Elimina Utente',
-    'users_delete_named' => "Elimina l'utente :userName",
+    'users_delete_named' => 'Elimina l\'utente :userName',
     'users_delete_warning' => 'Questo eliminerà completamente l\'utente \':userName\' dal sistema.',
     'users_delete_confirm' => 'Sei sicuro di voler eliminare questo utente?',
     'users_delete_success' => 'Utenti rimossi correttamente',
@@ -105,12 +125,42 @@ return [
     'users_edit_profile' => 'Modifica Profilo',
     'users_edit_success' => 'Utente aggiornato correttamente',
     'users_avatar' => 'Avatar Utente',
-    'users_avatar_desc' => "Quest'immagine dovrebbe essere approssimativamente 256px quadrata.",
+    'users_avatar_desc' => 'Quest\'immagine dovrebbe essere approssimativamente 256px quadrata.',
     'users_preferred_language' => 'Lingua Preferita',
+    'users_preferred_language_desc' => 'Questa opzione cambierà la lingua utilizzata per l\'interfaccia utente dell\'applicazione. Questo non influirà su alcun contenuto creato dall\'utente.',
     'users_social_accounts' => 'Account Social',
     'users_social_accounts_info' => 'Qui puoi connettere gli altri account per un accesso più veloce e semplice. Disconnettere un account qui non rimuoverà le altre sessioni. Revoca l\'accesso dal tuo profilo negli account social connessi.',
     'users_social_connect' => 'Connetti Account',
     'users_social_disconnect' => 'Disconnetti Account',
     'users_social_connected' => 'L\'account :socialAccount è stato connesso correttamente al tuo profilo.',
     'users_social_disconnected' => 'L\'account :socialAccount è stato disconnesso correttamente dal tuo profilo.',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index 832480d4cc0a04feb47cf3bdf8610940823f12b9..3b85303d2b879477e83f4309db786bfde8b152ed 100755 (executable)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => ':attribute deve essere accettato.',
     'active_url'           => ':attribute non è uno URL valido.',
     'after'                => ':attribute deve essere una data dopo il :date.',
@@ -35,12 +30,41 @@ return [
     'digits'               => 'Il campo :attribute deve essere di :digits numeri.',
     'digits_between'       => 'Il campo :attribute deve essere tra i numeri :min e :max.',
     'email'                => 'Il campo :attribute deve essere un indirizzo email valido.',
+    'ends_with' => ':attribute deve terminare con uno dei seguenti: :values',
     'filled'               => 'Il campo :attribute field is required.',
+    'gt'                   => [
+        'numeric' => ':attribute deve essere maggiore di :value.',
+        'file'    => ':attribute deve essere maggiore di :value kilobytes.',
+        'string'  => ':attribute deve essere maggiore di :value caratteri.',
+        'array'   => ':attribute deve avere più di :value elementi.',
+    ],
+    'gte'                  => [
+        'numeric' => ':attribute deve essere maggiore o uguale a :value.',
+        'file'    => ':attribute deve essere maggiore o uguale a :value kilobytes.',
+        'string'  => ':attribute deve essere maggiore o uguale a :value caratteri.',
+        'array'   => ':attribute deve avere :value elementi o più.',
+    ],
     'exists'               => 'Il campo :attribute non è valido.',
     'image'                => 'Il campo :attribute deve essere un\'immagine.',
+    'image_extension'      => ':attribute deve avere un\'estensione immagine valida e supportata.',
     'in'                   => 'Il campo :attribute selezionato non è valido.',
     'integer'              => 'Il campo :attribute deve essere un intero.',
     'ip'                   => 'Il campo :attribute deve essere un indirizzo IP valido.',
+    'ipv4'                 => ':attribute deve essere un indirizzo IPv4 valido.',
+    'ipv6'                 => ':attribute deve essere un indirizzo IPv6 valido.',
+    'json'                 => ':attribute deve essere una stringa JSON valida.',
+    'lt'                   => [
+        'numeric' => ':attribute deve essere inferiore a :value.',
+        'file'    => ':attribute deve essere inferiore a :value kilobytes.',
+        'string'  => ':attribute deve essere inferiore a :value caratteri.',
+        'array'   => ':attribute deve avere meno di :value elementi.',
+    ],
+    'lte'                  => [
+        'numeric' => ':attribute deve essere minore o uguale :value.',
+        'file'    => ':attribute deve essere minore o uguale a :value kilobytes.',
+        'string'  => ':attribute deve essere minore o uguale a :value caratteri.',
+        'array'   => ':attribute non deve avere più di :value elementi.',
+    ],
     'max'                  => [
         'numeric' => 'Il campo :attribute non deve essere maggiore di :max.',
         'file'    => 'Il campo :attribute non deve essere maggiore di :max kilobytes.',
@@ -54,7 +78,9 @@ return [
         'string'  => 'Il campo :attribute deve essere almeno :min caratteri.',
         'array'   => 'Il campo :attribute deve contenere almeno :min elementi.',
     ],
+    'no_double_extension'  => ':attribute deve avere solo un\'estensione.',
     'not_in'               => 'Il :attribute selezionato non è valido.',
+    'not_regex'            => 'Il formato di :attribute non è valido.',
     'numeric'              => ':attribute deve essere un numero.',
     'regex'                => 'Il formato di :attribute non è valido.',
     'required'             => 'Il campo :attribute è richiesto.',
@@ -74,35 +100,15 @@ return [
     'timezone'             => ':attribute deve essere una zona valida.',
     'unique'               => ':attribute è già preso.',
     'url'                  => 'Il formato :attribute non è valido.',
+    'uploaded'             => 'Il file non può essere caricato. Il server potrebbe non accettare file di questa dimensione.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => 'Conferma della password richiesta',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
index b907e0c63f5bb7ddb6a6f789cf72242066ef044f..de1ca1ac6c9cdea7475e8acc830ca5ed1856635c 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => 'がページを作成:',
     'page_create_notification'    => 'ページを作成しました',
@@ -37,4 +35,14 @@ return [
     'book_sort'                   => 'がブックの並び順を変更:',
     'book_sort_notification'      => '並び順を変更しました',
 
+    // Bookshelves
+    'bookshelf_create'            => 'created Bookshelf',
+    'bookshelf_create_notification'    => 'Bookshelf Successfully Created',
+    'bookshelf_update'                 => 'updated bookshelf',
+    'bookshelf_update_notification'    => 'Bookshelf Successfully Updated',
+    'bookshelf_delete'                 => 'deleted bookshelf',
+    'bookshelf_delete_notification'    => 'Bookshelf Successfully Deleted',
+
+    // Other
+    'commented_on'                => 'commented on',
 ];
index 4d5aee8b38b921b3d39857c18a2c1bb5eecd8ab7..d0db7d080f05f5b8930484f9e7cbf0da38b91a70 100644 (file)
@@ -1,21 +1,15 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => 'この資格情報は登録されていません。',
     'throttle' => 'ログイン試行回数が制限を超えました。:seconds秒後に再試行してください。',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => '新規登録',
     'log_in' => 'ログイン',
     'log_in_with' => ':socialDriverでログイン',
@@ -27,11 +21,13 @@ return [
     'email' => 'メールアドレス',
     'password' => 'パスワード',
     'password_confirm' => 'パスワード (確認)',
-    'password_hint' => '5文字以上である必要があります',
+    'password_hint' => '7文字以上である必要があります',
     'forgot_password' => 'パスワードをお忘れですか?',
     'remember_me' => 'ログイン情報を保存する',
     'ldap_email_hint' => 'このアカウントで使用するEメールアドレスを入力してください。',
     'create_account' => 'アカウント作成',
+    'already_have_account' => 'Already have an account?',
+    'dont_have_account' => 'Don\'t have an account?',
     'social_login' => 'SNSログイン',
     'social_registration' => 'SNS登録',
     'social_registration_text' => '他のサービスで登録 / ログインする',
@@ -43,23 +39,18 @@ return [
     'register_success' => '登録が完了し、ログインできるようになりました!',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => 'パスワードリセット',
     'reset_password_send_instructions' => '以下にEメールアドレスを入力すると、パスワードリセットリンクが記載されたメールが送信されます。',
     'reset_password_send_button' => 'リセットリンクを送信',
     'reset_password_sent_success' => ':emailへリセットリンクを送信しました。',
     'reset_password_success' => 'パスワードがリセットされました。',
-
     'email_reset_subject' => ':appNameのパスワードをリセット',
     'email_reset_text' => 'このメールは、パスワードリセットがリクエストされたため送信されています。',
     'email_reset_not_requested' => 'もしパスワードリセットを希望しない場合、操作は不要です。',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => ':appNameのメールアドレス確認',
     'email_confirm_greeting' => ':appNameへ登録してくださりありがとうございます!',
     'email_confirm_text' => '以下のボタンを押し、メールアドレスを確認してください:',
@@ -73,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => '登録時に受信したメールを確認し、確認リンクをクリックしてください。',
     'email_not_confirmed_resend' => 'Eメールが見つからない場合、以下のフォームから再送信してください。',
     'email_not_confirmed_resend_button' => '確認メールを再送信',
-];
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
+];
\ No newline at end of file
index e7b7b4a57536c403eca7545bd57833263fa8633f..feac9c460433576c99800535c91fb91f7e81f73c 100644 (file)
@@ -1,35 +1,37 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => 'キャンセル',
     'confirm' => '確認',
     'back' => '戻る',
     'save' => '保存',
     'continue' => '続ける',
     'select' => '選択',
+    'toggle_all' => 'Toggle All',
     'more' => 'その他',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => '名称',
     'description' => '概要',
     'role' => '権限',
+    'cover_image' => 'Cover image',
     'cover_image_description' => 'この画像は約 300x170px をする必要があります。',
-    /**
-     * Actions
-     */
+    
+    // Actions
     'actions' => '実行',
     'view' => '表示',
-    'reply' => '返信',
+    'view_all' => 'View All',
     'create' => '作成',
     'update' => '更新',
     'edit' => '編集',
     'sort' => '並び順',
     'move' => '移動',
+    'copy' => 'Copy',
+    'reply' => '返信',
     'delete' => '削除',
     'search' => '検索',
     'search_clear' => '検索をクリア',
@@ -37,28 +39,38 @@ return [
     'remove' => '削除',
     'add' => '追加',
 
+    // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
+    'sort_name' => 'Name',
+    'sort_created_at' => 'Created Date',
+    'sort_updated_at' => 'Updated Date',
 
-    /**
-     * Misc
-     */
+    // Misc
     'deleted_user' => '削除済みユーザ',
     'no_activity' => '表示するアクティビティがありません',
     'no_items' => 'アイテムはありません',
     'back_to_top' => '上に戻る',
     'toggle_details' => '概要の表示切替',
+    'toggle_thumbnails' => 'Toggle Thumbnails',
     'details' => '詳細',
     'grid_view' => 'グリッド形式',
     'list_view' => 'リスト形式',
+    'default' => 'Default',
+    'breadcrumb' => 'Breadcrumb',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => 'プロフィール表示',
     'edit_profile' => 'プロフィール編集',
 
-    /**
-     * Email Content
-     */
+    // Layout tabs
+    'tab_info' => 'Info',
+    'tab_content' => 'Content',
+
+    // Email Content
     'email_action_help' => '":actionText" をクリックできない場合、以下のURLをコピーしブラウザで開いてください:',
     'email_rights' => 'All rights reserved',
 ];
index 53a9cda1ba4f2698e33be5f772ee54de610ec721..65e8a7a79c9591ea0e187256cf6026597339bd1a 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => '画像を選択',
     'image_all' => 'すべて',
     'image_all_title' => '全ての画像を表示',
@@ -22,12 +23,11 @@ return [
     'image_upload_success' => '画像がアップロードされました',
     'image_update_success' => '画像が更新されました',
     'image_delete_success' => '画像が削除されました',
+    'image_upload_remove' => 'Remove',
 
-    /**
-     * Code editor
-     */
+    // Code Editor
     'code_editor' => 'プログラムブロック編集',
     'code_language' => 'プログラミング言語の選択',
     'code_content' => 'プログラム内容',
-    'code_save' => 'プログラムを保存'
+    'code_save' => 'プログラムを保存',
 ];
index f177154f442ff37d8bfed961dc09952c625fcb0f..4f1a855ff78a2a1414ce4082490716049f488edd 100644 (file)
@@ -1,14 +1,17 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => '最近作成',
     'recently_created_pages' => '最近作成されたページ',
     'recently_updated_pages' => '最近更新されたページ',
     'recently_created_chapters' => '最近作成されたチャプター',
     'recently_created_books' => '最近作成されたブック',
+    'recently_created_shelves' => 'Recently Created Shelves',
     'recently_update' => '最近更新',
     'recently_viewed' => '閲覧履歴',
     'recent_activity' => 'アクティビティ',
@@ -19,7 +22,6 @@ return [
     'meta_created_name' => '作成: :timeLength (:user)',
     'meta_updated' => '更新: :timeLength',
     'meta_updated_name' => '更新: :timeLength (:user)',
-    'x_pages' => ':count ページ',
     'entity_select' => 'エンティティ選択',
     'images' => '画像',
     'my_recent_drafts' => '最近の下書き',
@@ -32,17 +34,13 @@ return [
     'export_pdf' => 'PDF',
     'export_text' => 'テキストファイル',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => '権限',
     'permissions_intro' => 'この設定は各ユーザの役割よりも優先して適用されます。',
     'permissions_enable' => 'カスタム権限設定を有効にする',
     'permissions_save' => '権限を保存',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => '検索結果',
     'search_total_results_found' => ':count件見つかりました',
     'search_clear' => '検索をクリア',
@@ -53,11 +51,13 @@ return [
     'search_content_type' => '種類',
     'search_exact_matches' => '完全一致',
     'search_tags' => 'タグ検索',
+    'search_options' => 'Options',
     'search_viewed_by_me' => '自分が閲覧したことがある',
     'search_not_viewed_by_me' => '自分が閲覧したことがない',
     'search_permissions_set' => '権限が設定されている',
     'search_created_by_me' => '自分が作成した',
     'search_updated_by_me' => '自分が更新した',
+    'search_date_options' => 'Date Options',
     'search_updated_before' => '以前に更新',
     'search_updated_after' => '以降に更新',
     'search_created_before' => '以前に作成',
@@ -65,17 +65,49 @@ return [
     'search_set_date' => '日付を設定',
     'search_update' => 'フィルタを更新',
 
-    /**
-     * Books
-     */
+    // Shelves
+    'shelf' => 'Shelf',
+    'shelves' => 'Shelves',
+    'x_shelves' => ':count Shelf|:count Shelves',
+    'shelves_long' => 'Bookshelves',
+    'shelves_empty' => 'No shelves have been created',
+    'shelves_create' => 'Create New Shelf',
+    'shelves_popular' => 'Popular Shelves',
+    'shelves_new' => 'New Shelves',
+    'shelves_new_action' => 'New Shelf',
+    'shelves_popular_empty' => 'The most popular shelves will appear here.',
+    'shelves_new_empty' => 'The most recently created shelves will appear here.',
+    'shelves_save' => 'Save Shelf',
+    'shelves_books' => 'Books on this shelf',
+    'shelves_add_books' => 'Add books to this shelf',
+    'shelves_drag_books' => 'Drag books here to add them to this shelf',
+    'shelves_empty_contents' => 'This shelf has no books assigned to it',
+    'shelves_edit_and_assign' => 'Edit shelf to assign books',
+    'shelves_edit_named' => 'Edit Bookshelf :name',
+    'shelves_edit' => 'Edit Bookshelf',
+    'shelves_delete' => 'Delete Bookshelf',
+    'shelves_delete_named' => 'Delete Bookshelf :name',
+    'shelves_delete_explain' => "This will delete the bookshelf with the name ':name'. Contained books will not be deleted.",
+    'shelves_delete_confirmation' => 'Are you sure you want to delete this bookshelf?',
+    'shelves_permissions' => 'Bookshelf Permissions',
+    'shelves_permissions_updated' => 'Bookshelf Permissions Updated',
+    'shelves_permissions_active' => 'Bookshelf Permissions Active',
+    'shelves_copy_permissions_to_books' => 'Copy Permissions to Books',
+    'shelves_copy_permissions' => 'Copy Permissions',
+    'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this bookshelf to all books contained within. Before activating, ensure any changes to the permissions of this bookshelf have been saved.',
+    'shelves_copy_permission_success' => 'Bookshelf permissions copied to :count books',
+
+    // Books
     'book' => 'Book',
     'books' => 'ブック',
+    'x_books' => ':count ブック',
     'books_empty' => 'まだブックは作成されていません',
     'books_popular' => '人気のブック',
-    'x_books' => ':count ブック',
     'books_recent' => '最近のブック',
     'books_new' => '新しいブック',
+    'books_new_action' => 'New Book',
     'books_popular_empty' => 'ここに人気のブックが表示されます。',
+    'books_new_empty' => 'The most recently created books will appear here.',
     'books_create' => '新しいブックを作成',
     'books_delete' => 'ブックを削除',
     'books_delete_named' => 'ブック「:bookName」を削除',
@@ -89,7 +121,6 @@ return [
     'books_permissions_updated' => 'ブックの権限を更新しました',
     'books_empty_contents' => 'まだページまたはチャプターが作成されていません。',
     'books_empty_create_page' => '新しいページを作成',
-    'books_empty_or' => 'または',
     'books_empty_sort_current_book' => 'ブックの並び順を変更',
     'books_empty_add_chapter' => 'チャプターを追加',
     'books_permissions_active' => 'ブックの権限は有効です',
@@ -97,16 +128,19 @@ return [
     'books_navigation' => '目次',
     'books_sort' => '並び順を変更',
     'books_sort_named' => 'ブック「:bookName」を並び替え',
+    'books_sort_name' => 'Sort by Name',
+    'books_sort_created' => 'Sort by Created Date',
+    'books_sort_updated' => 'Sort by Updated Date',
+    'books_sort_chapters_first' => 'Chapters First',
+    'books_sort_chapters_last' => 'Chapters Last',
     'books_sort_show_other' => '他のブックを表示',
     'books_sort_save' => '並び順を保存',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => 'チャプター',
     'chapters' => 'チャプター',
-    'chapters_popular' => '人気のチャプター',
     'x_chapters' => ':count チャプター',
+    'chapters_popular' => '人気のチャプター',
     'chapters_new' => 'チャプターを作成',
     'chapters_create' => 'チャプターを作成',
     'chapters_delete' => 'チャプターを削除',
@@ -125,11 +159,10 @@ return [
     'chapters_permissions_success' => 'チャプターの権限を更新しました',
     'chapters_search_this' => 'このチャプターを検索',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => 'ページ',
     'pages' => 'ページ',
+    'x_pages' => ':count ページ',
     'pages_popular' => '人気のページ',
     'pages_new' => 'ページを作成',
     'pages_attachments' => '添付',
@@ -143,7 +176,7 @@ return [
     'pages_delete_confirm' => 'このページを削除してもよろしいですか?',
     'pages_delete_draft_confirm' => 'このページの下書きを削除してもよろしいですか?',
     'pages_editing_named' => 'ページ :pageName を編集',
-    'pages_edit_toggle_header' => 'ヘッダーの表示切替',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => '下書きを保存',
     'pages_edit_draft' => 'ページの下書きを編集',
     'pages_editing_draft' => '下書きを編集中',
@@ -161,17 +194,24 @@ return [
     'pages_md_preview' => 'プレビュー',
     'pages_md_insert_image' => '画像を挿入',
     'pages_md_insert_link' => 'エンティティへのリンクを挿入',
+    'pages_md_insert_drawing' => 'Insert Drawing',
     'pages_not_in_chapter' => 'チャプターが設定されていません',
     'pages_move' => 'ページを移動',
     'pages_move_success' => 'ページを ":parentName" へ移動しました',
+    'pages_copy' => 'Copy Page',
+    'pages_copy_desination' => 'Copy Destination',
+    'pages_copy_success' => 'Page successfully copied',
     'pages_permissions' => 'ページの権限設定',
     'pages_permissions_success' => 'ページの権限を更新しました',
+    'pages_revision' => 'Revision',
     'pages_revisions' => '編集履歴',
     'pages_revisions_named' => ':pageName のリビジョン',
     'pages_revision_named' => ':pageName のリビジョン',
     'pages_revisions_created_by' => '作成者',
     'pages_revisions_date' => '日付',
     'pages_revisions_number' => 'リビジョン',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
     'pages_revisions_changelog' => '説明',
     'pages_revisions_changes' => '変更点',
     'pages_revisions_current' => '現在のバージョン',
@@ -193,16 +233,21 @@ return [
         'message' => ':start :time. 他のユーザによる更新を上書きしないよう注意してください。',
     ],
     'pages_draft_discarded' => '下書きが破棄されました。エディタは現在の内容へ復元されています。',
+    'pages_specific' => 'Specific Page',
+    'pages_is_template' => 'Page Template',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => 'タグ',
+    'chapter_tags' => 'Chapter Tags',
+    'book_tags' => 'Book Tags',
+    'shelf_tags' => 'Shelf Tags',
     'tag' => 'タグ',
-    'tags' =>  '',
+    'tags' =>  'Tags',
+    'tag_name' =>  'Tag Name',
     'tag_value' => '内容 (オプション)',
     'tags_explain' => "タグを設定すると、コンテンツの管理が容易になります。\nより高度な管理をしたい場合、タグに内容を設定できます。",
     'tags_add' => 'タグを追加',
+    'tags_remove' => 'Remove this tag',
     'attachments' => '添付ファイル',
     'attachments_explain' => 'ファイルをアップロードまたはリンクを添付することができます。これらはサイドバーで確認できます。',
     'attachments_explain_instant_save' => 'この変更は即座に保存されます。',
@@ -228,19 +273,22 @@ return [
     'attachments_file_uploaded' => 'ファイルがアップロードされました',
     'attachments_file_updated' => 'ファイルが更新されました',
     'attachments_link_attached' => 'リンクがページへ添付されました',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => ':time前に作成',
     'profile_created_content' => '作成したコンテンツ',
     'profile_not_created_pages' => ':userNameはページを作成していません',
     'profile_not_created_chapters' => ':userNameはチャプターを作成していません',
     'profile_not_created_books' => ':userNameはブックを作成していません',
+    'profile_not_created_shelves' => ':userName has not created any shelves',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => 'コメント',
     'comments' => 'コメント',
     'comment_add' => 'コメント追加',
@@ -258,10 +306,9 @@ return [
     'comment_delete_confirm' => '本当にこのコメントを削除しますか?',
     'comment_in_reply_to' => ':commentIdへ返信',
 
-     /**
-     * Revision
-     */
+    // Revision
     'revision_delete_confirm' => 'このリビジョンを削除しますか?',
+    'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
     'revision_delete_success' => 'リビジョンを削除しました',
     'revision_cannot_delete_latest' => '最新のリビジョンを削除できません。'
-];
+];
\ No newline at end of file
index 8c13d4474c12926b80456213d595f8781359c932..c42a773b66eea5c22c851df1eb2204f779f68377 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => 'リクエストされたページへの権限がありません。',
     'permissionJson' => '要求されたアクションを実行する権限がありません。',
@@ -20,6 +18,7 @@ return [
     'ldap_extension_not_installed' => 'LDAP PHP extensionがインストールされていません',
     'ldap_cannot_connect' => 'LDAPサーバに接続できませんでした',
     'social_no_action_defined' => 'アクションが定義されていません',
+    'social_login_bad_response' => "Error received during :socialAccount login: \n:error",
     'social_account_in_use' => ':socialAccountアカウントは既に使用されています。:socialAccountのオプションからログインを試行してください。',
     'social_account_email_in_use' => ':emailは既に使用されています。ログイン後、プロフィール設定から:socialAccountアカウントを接続できます。',
     'social_account_existing' => 'アカウント:socialAccountは既にあなたのプロフィールに接続されています。',
@@ -28,23 +27,29 @@ return [
     'social_account_register_instructions' => 'まだアカウントをお持ちでない場合、:socialAccountオプションから登録できます。',
     'social_driver_not_found' => 'Social driverが見つかりません。',
     'social_driver_not_configured' => 'あなたの:socialAccount設定は正しく構成されていません。',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => 'ファイルパス :filePath へアップロードできませんでした。サーバ上での書き込みを許可してください。',
     'cannot_get_image_from_url' => ':url から画像を取得できませんでした。',
     'cannot_create_thumbs' => 'このサーバはサムネイルを作成できません。GD PHP extensionがインストールされていることを確認してください。',
     'server_upload_limit' => 'このサイズの画像をアップロードすることは許可されていません。ファイルサイズを小さくし、再試行してください。',
+    'uploaded'  => 'The server does not allow uploads of this size. Please try a smaller file size.',
     'image_upload_error' => '画像アップロード時にエラーが発生しました。',
+    'image_upload_type_error' => 'The image type being uploaded is invalid',
     'file_upload_timeout' => 'ファイルのアップロードがタイムアウトしました。',
 
     // Attachments
     'attachment_page_mismatch' => '添付を更新するページが一致しません',
+    'attachment_not_found' => 'Attachment not found',
 
     // Pages
     'page_draft_autosave_fail' => '下書きの保存に失敗しました。インターネットへ接続してください。',
+    'page_custom_home_deletion' => 'Cannot delete a page while it is set as a homepage',
 
     // Entities
     'entity_not_found' => 'エンティティが見つかりません',
+    'bookshelf_not_found' => 'Bookshelf not found',
     'book_not_found' => 'ブックが見つかりません',
     'page_not_found' => 'ページが見つかりません',
     'chapter_not_found' => 'チャプターが見つかりません',
@@ -60,6 +65,14 @@ return [
     'role_cannot_be_edited' => 'この役割は編集できません',
     'role_system_cannot_be_deleted' => 'この役割はシステムで管理されているため、削除できません',
     'role_registration_default_cannot_delete' => 'この役割を登録時のデフォルトに設定することはできません',
+    'role_cannot_remove_only_admin' => 'This user is the only user assigned to the administrator role. Assign the administrator role to another user before attempting to remove it here.',
+
+    // Comments
+    'comment_list' => 'An error occurred while fetching the comments.',
+    'cannot_add_comment_to_draft' => 'You cannot add comments to a draft.',
+    'comment_add' => 'An error occurred while adding / updating the comment.',
+    'comment_delete' => 'An error occurred while deleting the comment.',
+    'empty_comment' => 'Cannot add an empty comment.',
 
     // Error pages
     '404_page_not_found' => 'ページが見つかりません',
@@ -68,4 +81,5 @@ return [
     'error_occurred' => 'エラーが発生しました',
     'app_down' => ':appNameは現在停止しています',
     'back_soon' => '回復までしばらくお待ちください。',
+
 ];
index 1ebcef722e6ec64c4af211ef7f07ba986293cba6..725ec6b4104c87c65082b7bf817dd2db7a3c1f8a 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; 前',
     'next'     => '次 &raquo;',
 
index 17c82e299e51ab639b298db15c529212e19d4ee2..3531c73c74a32df33ed17ec3fa805128d70b33bf 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => 'パスワードは6文字以上である必要があります。',
     'user' => "このEメールアドレスに一致するユーザが見つかりませんでした。",
     'token' => 'このパスワードリセットトークンは無効です。',
index f9c9c5e86e66460b3e85bf2d52856a34fc8a38ff..34eb469e96d1984ad9faadcb0d63d92f0ee43203 100644 (file)
@@ -1,56 +1,70 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
-
+    // Common Messages
     'settings' => '設定',
     'settings_save' => '設定を保存',
     'settings_save_success' => '設定を保存しました',
 
-    /**
-     * App settings
-     */
-
-    'app_settings' => 'アプリケーション設定',
+    // App Settings
+    'app_customization' => 'Customization',
+    'app_features_security' => 'Features & Security',
     'app_name' => 'アプリケーション名',
     'app_name_desc' => 'この名前はヘッダーやEメール内で表示されます。',
     'app_name_header' => 'ヘッダーにアプリケーション名を表示する',
+    'app_public_access' => 'Public Access',
+    'app_public_access_desc' => 'Enabling this option will allow visitors, that are not logged-in, to access content in your BookStack instance.',
+    'app_public_access_desc_guest' => 'Access for public visitors can be controlled through the "Guest" user.',
+    'app_public_access_toggle' => 'Allow public access',
     'app_public_viewing' => 'アプリケーションを公開する',
     'app_secure_images' => '画像アップロード時のセキュリティを強化',
+    'app_secure_images_toggle' => 'Enable higher security image uploads',
     'app_secure_images_desc' => 'パフォーマンスの観点から、全ての画像が公開になっています。このオプションを有効にすると、画像URLの先頭にランダムで推測困難な文字列が追加され、アクセスを困難にします。',
     'app_editor' => 'ページエディタ',
     'app_editor_desc' => 'ここで選択されたエディタを全ユーザが使用します。',
     'app_custom_html' => 'カスタムheadタグ',
     'app_custom_html_desc' => 'スタイルシートやアナリティクスコード追加したい場合、ここを編集します。これは<head>の最下部に挿入されます。',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'ロゴ',
     'app_logo_desc' => '高さ43pxで表示されます。これを上回る場合、自動で縮小されます。',
     'app_primary_color' => 'プライマリカラー',
     'app_primary_color_desc' => '16進数カラーコードで入力します。空にした場合、デフォルトの色にリセットされます。',
+    'app_homepage' => 'Application Homepage',
+    'app_homepage_desc' => 'Select a view to show on the homepage instead of the default view. Page permissions are ignored for selected pages.',
+    'app_homepage_select' => 'Select a page',
     'app_disable_comments' => 'コメントを無効にする',
+    'app_disable_comments_toggle' => 'Disable comments',
     'app_disable_comments_desc' => 'アプリケーション内のすべてのページのコメントを無効にします。既存のコメントは表示されません。',
 
-    /**
-     * Registration settings
-     */
-
+    // Registration Settings
     'reg_settings' => '登録設定',
-    'reg_allow' => '新規登録を許可',
+    'reg_enable' => 'Enable Registration',
+    'reg_enable_toggle' => 'Enable registration',
+    'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
     'reg_default_role' => '新規登録時のデフォルト役割',
-    'reg_confirm_email' => 'Eメール認証を必須にする',
+    'reg_email_confirmation' => 'Email Confirmation',
+    'reg_email_confirmation_toggle' => 'Require email confirmation',
     'reg_confirm_email_desc' => 'ドメイン制限を有効にしている場合はEメール認証が必須となり、この項目は無視されます。',
     'reg_confirm_restrict_domain' => 'ドメイン制限',
     'reg_confirm_restrict_domain_desc' => '特定のドメインのみ登録できるようにする場合、以下にカンマ区切りで入力します。設定された場合、Eメール認証が必須になります。<br>登録後、ユーザは自由にEメールアドレスを変更できます。',
     'reg_confirm_restrict_domain_placeholder' => '制限しない',
 
-    /**
-     * Role settings
-     */
+    // Maintenance settings
+    'maint' => 'Maintenance',
+    'maint_image_cleanup' => 'Cleanup Images',
+    'maint_image_cleanup_desc' => "Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.",
+    'maint_image_cleanup_ignore_revisions' => 'Ignore images in revisions',
+    'maint_image_cleanup_run' => 'Run Cleanup',
+    'maint_image_cleanup_warning' => ':count potentially unused images were found. Are you sure you want to delete these images?',
+    'maint_image_cleanup_success' => ':count potentially unused images found and deleted!',
+    'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!',
 
+    // Role Settings
     'roles' => '役割',
     'role_user_roles' => '役割',
     'role_create' => '役割を作成',
@@ -65,14 +79,17 @@ return [
     'role_details' => '概要',
     'role_name' => '役割名',
     'role_desc' => '役割の説明',
+    'role_external_auth_id' => 'External Authentication IDs',
     'role_system' => 'システム権限',
     'role_manage_users' => 'ユーザ管理',
     'role_manage_roles' => '役割と権限の管理',
     'role_manage_entity_permissions' => '全てのブック, チャプター, ページに対する権限の管理',
     'role_manage_own_entity_permissions' => '自身のブック, チャプター, ページに対する権限の管理',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'アプリケーション設定の管理',
     'role_asset' => 'アセット権限',
     'role_asset_desc' => '各アセットに対するデフォルトの権限を設定します。ここで設定した権限が優先されます。',
+    'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.',
     'role_all' => '全て',
     'role_own' => '自身',
     'role_controlled_by_asset' => 'このアセットに対し、右記の操作を許可:',
@@ -81,16 +98,22 @@ return [
     'role_users' => 'この役割を持つユーザ',
     'role_users_none' => 'この役割が付与されたユーザは居ません',
 
-    /**
-     * Users
-     */
-
+    // Users
     'users' => 'ユーザ',
     'user_profile' => 'ユーザプロフィール',
     'users_add_new' => 'ユーザを追加',
     'users_search' => 'ユーザ検索',
+    'users_details' => 'User Details',
+    'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.',
+    'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.',
     'users_role' => 'ユーザ役割',
+    'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
+    'users_password' => 'User Password',
+    'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 6 characters long.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => '外部認証ID',
+    'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your LDAP system.',
     'users_password_warning' => 'パスワードを変更したい場合のみ入力してください',
     'users_system_public' => 'このユーザはアプリケーションにアクセスする全てのゲストを表します。ログインはできませんが、自動的に割り当てられます。',
     'users_delete' => 'ユーザを削除',
@@ -104,11 +127,40 @@ return [
     'users_avatar' => 'アバター',
     'users_avatar_desc' => '256pxの正方形である必要があります。',
     'users_preferred_language' => '使用言語',
+    'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
     'users_social_accounts' => 'ソーシャルアカウント',
     'users_social_accounts_info' => 'アカウントを接続すると、ログインが簡単になります。ここでアカウントの接続を解除すると、そのアカウントを経由したログインを禁止できます。接続解除後、各ソーシャルアカウントの設定にてこのアプリケーションへのアクセス許可を解除してください。',
     'users_social_connect' => 'アカウントを接続',
     'users_social_disconnect' => 'アカウントを接続解除',
     'users_social_connected' => '「:socialAccount」がプロフィールに接続されました。',
-    'users_social_disconnected' => '「:socialAccount」がプロフィールから接続解除されました。'
+    'users_social_disconnected' => '「:socialAccount」がプロフィールから接続解除されました。',
 
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index e0fa3cb2c44457ae7ed2293c3203d3d35121b027..231bdfa0b8f34f6ac7098f2ba1304d4ce465e6c5 100644 (file)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => ':attributeに同意する必要があります。',
     'active_url'           => ':attributeは正しいURLではありません。',
     'after'                => ':attributeは:date以降である必要があります。',
@@ -35,12 +30,41 @@ return [
     'digits'               => ':attributeは:digitsデジットである必要があります',
     'digits_between'       => ':attributeは:min〜:maxである必要があります。',
     'email'                => ':attributeは正しいEメールアドレスである必要があります。',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => ':attributeは必須です。',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => '選択された:attributeは不正です。',
     'image'                => ':attributeは画像である必要があります。',
+    'image_extension'      => 'The :attribute must have a valid & supported image extension.',
     'in'                   => '選択された:attributeは不正です。',
     'integer'              => ':attributeは数値である必要があります。',
     'ip'                   => ':attributeは正しいIPアドレスである必要があります。',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => ':attributeは:maxを越えることができません。',
         'file'    => ':attributeは:maxキロバイトを越えることができません。',
@@ -54,7 +78,9 @@ return [
         'string'  => ':attributeは:min文字以上である必要があります。',
         'array'   => ':attributeは:min個以上である必要があります。',
     ],
+    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => '選択された:attributeは不正です。',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attributeは数値である必要があります。',
     'regex'                => ':attributeのフォーマットは不正です。',
     'required'             => ':attributeは必須です。',
@@ -74,35 +100,15 @@ return [
     'timezone'             => ':attributeは正しいタイムゾーンである必要があります。',
     'unique'               => ':attributeは既に使用されています。',
     'url'                  => ':attributeのフォーマットは不正です。',
+    'uploaded'             => 'The file could not be uploaded. The server may not accept files of this size.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => 'パスワードの確認は必須です。',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
similarity index 72%
rename from resources/lang/kr/activities.php
rename to resources/lang/ko/activities.php
index d2c46a00784794156b95307e4d3975684c2ce370..5baeed3c8e456ded7d9eb7bb5a1bd941adf6cd63 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => '페이지 생성',
     'page_create_notification'    => '페이지를 만들었습니다.',
@@ -37,4 +35,14 @@ return [
     'book_sort'                   => '책 정렬',
     'book_sort_notification'      => '책을 정렬하였습니다.',
 
+    // Bookshelves
+    'bookshelf_create'            => 'created Bookshelf',
+    'bookshelf_create_notification'    => 'Bookshelf Successfully Created',
+    'bookshelf_update'                 => 'updated bookshelf',
+    'bookshelf_update_notification'    => 'Bookshelf Successfully Updated',
+    'bookshelf_delete'                 => 'deleted bookshelf',
+    'bookshelf_delete_notification'    => 'Bookshelf Successfully Deleted',
+
+    // Other
+    'commented_on'                => 'commented on',
 ];
similarity index 75%
rename from resources/lang/kr/auth.php
rename to resources/lang/ko/auth.php
index 671ddc654109ee6c06732f679d34a2960fa7bd87..86b55dc2c860ade5a6c87983efc36779d0dc2a77 100644 (file)
@@ -1,21 +1,15 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => '이 자격 증명은 등록되어 있지 않습니다.',
     'throttle' => '로그인 시도 횟수 제한을 초과했습니다. :seconds초 후에 다시 시도하십시오.',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => '신규등록',
     'log_in' => '로그인',
     'log_in_with' => ':socialDriver에 로그인',
@@ -27,11 +21,13 @@ return [
     'email' => '이메일',
     'password' => '비밀번호',
     'password_confirm' => '비밀번호 (확인)',
-    'password_hint' => '5자 이상이어야 합니다.',
+    'password_hint' => '7자 이상이어야 합니다.',
     'forgot_password' => '비밀번호를 잊으셨습니까?',
     'remember_me' => '자동로그인',
     'ldap_email_hint' => '이 계정에서 사용하는 이메일을 입력해 주세요.',
     'create_account' => '계정 만들기',
+    'already_have_account' => 'Already have an account?',
+    'dont_have_account' => 'Don\'t have an account?',
     'social_login' => 'SNS로그인',
     'social_registration' => 'SNS등록',
     'social_registration_text' => '다른 서비스를 사용하여 등록하고 로그인.',
@@ -43,23 +39,18 @@ return [
     'register_success' => '등록을 완료하고 로그인 할 수 있습니다!',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => '암호 재설정',
     'reset_password_send_instructions' => '다음에 메일 주소를 입력하면 비밀번호 재설정 링크가 포함 된 이메일이 전송됩니다.',
     'reset_password_send_button' => '재설정 링크 보내기',
     'reset_password_sent_success' => ':email로 재설정 링크를 보냈습니다.',
     'reset_password_success' => '비밀번호가 재설정되었습니다.',
-
     'email_reset_subject' => ':appName 암호를 재설정',
     'email_reset_text' => '귀하의 계정에 대한 비밀번호 재설정 요청을 받았기 때문에 본 이메일이 발송되었습니다.',
     'email_reset_not_requested' => '암호 재설정을 요청하지 않은 경우 더 이상의 조치는 필요하지 않습니다.',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => ':appName의 이메일 주소 확인',
     'email_confirm_greeting' => ':appName에 가입 ​​해 주셔서 감사합니다!',
     'email_confirm_text' => '다음 버튼을 눌러 이메일 주소를 확인하십시오',
@@ -73,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => '등록시 받은 이메일을 확인하고 확인 링크를 클릭하십시오.',
     'email_not_confirmed_resend' => '메일이 없으면 아래 양식을 통해 다시 제출하십시오.',
     'email_not_confirmed_resend_button' => '확인 메일을 다시 전송',
-];
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
+];
\ No newline at end of file
similarity index 68%
rename from resources/lang/kr/common.php
rename to resources/lang/ko/common.php
index e040c6e5ab778bc7d19751d78478a9efda3f6aea..31d3e50241934331925ffcb985bf8639842f36a7 100644 (file)
@@ -1,31 +1,30 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => '취소',
     'confirm' => '확인',
     'back' => '뒤로',
     'save' => '저장',
     'continue' => '계속하기',
     'select' => '선택',
+    'toggle_all' => 'Toggle All',
     'more' => '더보기',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => '이름',
     'description' => '설명',
     'role' => '역할',
     'cover_image' => '대표 이미지',
     'cover_image_description' => '이 이미지는 약 440x250px 정도의 크기여야 합니다.',
     
-    /**
-     * Actions
-     */
+    // Actions
     'actions' => 'Actions',
     'view' => '뷰',
+    'view_all' => 'View All',
     'create' => '생성',
     'update' => '업데이트',
     'edit' => '수정',
@@ -40,9 +39,16 @@ return [
     'remove' => '제거',
     'add' => '추가',
 
-    /**
-     * Misc
-     */
+    // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
+    'sort_name' => 'Name',
+    'sort_created_at' => 'Created Date',
+    'sort_updated_at' => 'Updated Date',
+
+    // Misc
     'deleted_user' => '삭제된 사용자',
     'no_activity' => '활동내역이 없음',
     'no_items' => '사용가능한 항목이 없음',
@@ -53,16 +59,18 @@ return [
     'grid_view' => '그리드 뷰',
     'list_view' => '리스트뷰',
     'default' => '기본설정',
+    'breadcrumb' => 'Breadcrumb',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => '프로파일 보기',
     'edit_profile' => '프로파일 수정하기',
 
-    /**
-     * Email Content
-     */
+    // Layout tabs
+    'tab_info' => 'Info',
+    'tab_content' => 'Content',
+
+    // Email Content
     'email_action_help' => '":actionText"버튼을 클릭하는 데 문제가 있으면 아래 URL을 복사하여 웹 브라우저에 붙여 넣으십시오:',
     'email_rights' => 'All rights reserved',
-];
\ No newline at end of file
+];
similarity index 93%
rename from resources/lang/kr/components.php
rename to resources/lang/ko/components.php
index ffb1f6bea4e6cdd677cc53846abd6036af0e9f32..fd7839e2f2c38161e35d68982541bfab4a0d742c 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => '이미지 선택',
     'image_all' => '전체',
     'image_all_title' => '모든 이미지 보기',
@@ -24,9 +25,7 @@ return [
     'image_delete_success' => '이미지가 삭제되었습니다.',
     'image_upload_remove' => '제거',
 
-    /**
-     * Code editor
-     */
+    // Code Editor
     'code_editor' => '코드 수정',
     'code_language' => '코드 언어',
     'code_content' => '코드 내용',
similarity index 90%
rename from resources/lang/kr/entities.php
rename to resources/lang/ko/entities.php
index 394027564eec9e4802214380c09aca097e848cbd..4e2692bc57c00ae1b6a504ba9fe642a15ff813f4 100644 (file)
@@ -1,14 +1,17 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => '최근작성',
     'recently_created_pages' => '최근 작성된 페이지',
     'recently_updated_pages' => '최근 업데이트된 페이지',
     'recently_created_chapters' => '최근 만들어진 챕터',
     'recently_created_books' => '최근 만들어진 책',
+    'recently_created_shelves' => 'Recently Created Shelves',
     'recently_update' => '최근 작성',
     'recently_viewed' => '검색 기록',
     'recent_activity' => '최근 활동',
@@ -31,17 +34,13 @@ return [
     'export_pdf' => 'PDF 파일',
     'export_text' => '일반 텍스트 파일',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => '권한',
     'permissions_intro' => '이 설정은 각 사용자의 역할보다 우선하여 적용됩니다.',
     'permissions_enable' => '커스텀 권한 활성화',
     'permissions_save' => '권한 저장',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => '검색 결과',
     'search_total_results_found' => ':count 개의 결과를 찾았습니다.|총 :count 개의 결과를 찾았습니다.',
     'search_clear' => '검색기록 초기화',
@@ -66,16 +65,16 @@ return [
     'search_set_date' => '날짜 설정',
     'search_update' => '검색 업데이트',
 
-    /**
-     * Shelves
-     */
+    // Shelves
     'shelf' => '책꽃이',
     'shelves' => '책꽃이',
+    'x_shelves' => ':count Shelf|:count Shelves',
     'shelves_long' => '책꽃이',
     'shelves_empty' => '책꽃이가 만들어지지 않았습니다.',
     'shelves_create' => '새책꽃이 만들기',
     'shelves_popular' => '인기있는 책꽃이',
     'shelves_new' => '새로운 책꽃이',
+    'shelves_new_action' => 'New Shelf',
     'shelves_popular_empty' => '인기있는 책꽃이가 여기에 나타납니다.',
     'shelves_new_empty' => '가장 최근에 만들어진 책꽃이가 여기에 나타납니다.',
     'shelves_save' => '책꽃이 저장',
@@ -98,9 +97,7 @@ return [
     'shelves_copy_permissions_explain' => '이 책꽂이의 현재 권한 설정이 안에 포함 된 모든 책에 적용됩니다. 활성화하기 전에이 책꽂이의 사용 권한이 변경되었는지 확인하십시오.',
     'shelves_copy_permission_success' => '책꽃이의 권한이 :count 개의 책에 복사되었습니다.',
 
-    /**
-     * Books
-     */
+    // Books
     'book' => '책',
     'books' => '책들',
     'x_books' => ':count 책|:count 책들',
@@ -108,6 +105,7 @@ return [
     'books_popular' => '인기있는 책',
     'books_recent' => '최근 책',
     'books_new' => '새로운 책',
+    'books_new_action' => 'New Book',
     'books_popular_empty' => '가장 인기있는 책이 여기에 보입니다.',
     'books_new_empty' => '가장 최근에 만든 책이 여기에 표시됩니다.',
     'books_create' => '새로운 책 만들기',
@@ -123,7 +121,6 @@ return [
     'books_permissions_updated' => '책 권한이 업데이트 되었습니다.',
     'books_empty_contents' => '이 책에 대한 페이지 또는 장이 작성되지 않았습니다.',
     'books_empty_create_page' => '새로운 페이지 만들기',
-    'books_empty_or' => '또는',
     'books_empty_sort_current_book' => '현제 책 정렬하기',
     'books_empty_add_chapter' => '챕터 추가하기',
     'books_permissions_active' => '책 권한 활성화',
@@ -131,12 +128,15 @@ return [
     'books_navigation' => '책 네비게이션',
     'books_sort' => '책 구성 정렬하기',
     'books_sort_named' => ':bookName 책 정렬하기',
+    'books_sort_name' => 'Sort by Name',
+    'books_sort_created' => 'Sort by Created Date',
+    'books_sort_updated' => 'Sort by Updated Date',
+    'books_sort_chapters_first' => 'Chapters First',
+    'books_sort_chapters_last' => 'Chapters Last',
     'books_sort_show_other' => '다른책 보기',
     'books_sort_save' => '새로운 순서 저장',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => '챕터',
     'chapters' => '챕터',
     'x_chapters' => ':count 개 챕터|:count 챔터들',
@@ -159,9 +159,7 @@ return [
     'chapters_permissions_success' => '챕터 권한 수정됨',
     'chapters_search_this' => '이 챕터 찾기',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => '페이지',
     'pages' => '페이지들',
     'x_pages' => ':count 개의 페이지|:count 개의 페이지들',
@@ -178,7 +176,7 @@ return [
     'pages_delete_confirm' => '정말로 이 페이지를 지우시겠습니까?',
     'pages_delete_draft_confirm' => '정말로 초안페이지를 지우시겠습니까?',
     'pages_editing_named' => ':pageName 페이지 수정',
-    'pages_edit_toggle_header' => '헤더 숨김/보이기',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => '초안 저장',
     'pages_edit_draft' => '페이지 초안 수정',
     'pages_editing_draft' => '초안 수정중',
@@ -212,6 +210,8 @@ return [
     'pages_revisions_created_by' => 'Created By',
     'pages_revisions_date' => '변경일',
     'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
     'pages_revisions_changelog' => '변경내역',
     'pages_revisions_changes' => '변경사항 보기',
     'pages_revisions_current' => '현재 버전',
@@ -234,19 +234,20 @@ return [
     ],
     'pages_draft_discarded' => '초안이 삭제되었습니다. 편집기가 현재 페이지 작성자로 업데이트되었습니다.',
     'pages_specific' => '특정 페이지',
+    'pages_is_template' => 'Page Template',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => '페이지 테그',
     'chapter_tags' => '챕터 테그',
     'book_tags' => '책 테그',
     'shelf_tags' => '책꽃이 테그',
     'tag' => '테그',
     'tags' =>  '테그들',
+    'tag_name' =>  'Tag Name',
     'tag_value' => '테그 값 (선택사항)',
     'tags_explain' => "컨텐츠를 더 잘 분류하기 위해 테그를 추가하세요! \n 보다 상세한 구성을 위해 태그값을 할당 할 수 있습니다.",
     'tags_add' => '다른 테그 추가',
+    'tags_remove' => 'Remove this tag',
     'attachments' => '첨부',
     'attachments_explain' => '일부 파일을 업로드하거나 페이지에 표시 할 링크를 첨부하십시오. 페이지 사이드 바에 표시됩니다.',
     'attachments_explain_instant_save' => '변경 사항은 즉시 저장됩니다.',
@@ -272,19 +273,22 @@ return [
     'attachments_file_uploaded' => '파일이 성공적으로 업로드 되었습니다.',
     'attachments_file_updated' => '파일이 성공적으로 업데이트 되었습니다.',
     'attachments_link_attached' => '링크가 성공적으로 페이지에 첨부되었습니다.',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => ':time 전에 작성',
     'profile_created_content' => '생성한 컨텐츠',
     'profile_not_created_pages' => ':userName가 작성한 페이지가 없습니다.',
     'profile_not_created_chapters' => ':userName가 작성한 챕터가 없습니다.',
     'profile_not_created_books' => ':userName가 작성한 책이 없습니다.',
+    'profile_not_created_shelves' => ':userName has not created any shelves',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => '코멘트',
     'comments' => '코멘트들',
     'comment_add' => '코멘트 추가',
@@ -302,10 +306,9 @@ return [
     'comment_delete_confirm' => '정말로 코멘트를 지우시겠습니까?',
     'comment_in_reply_to' => ':commentId 응답',
 
-    /**
-     * Revision
-     */
+    // Revision
     'revision_delete_confirm' => '해당 개정판을 지우시겠습니까??',
+    'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
     'revision_delete_success' => '개정판 삭제성공',
     'revision_cannot_delete_latest' => '최신버전은 지울수 없습니다.'
 ];
\ No newline at end of file
similarity index 94%
rename from resources/lang/kr/errors.php
rename to resources/lang/ko/errors.php
index 59dbfda2ba149b881b151544a06470b3aa446fd4..1a88dd4e31017b148e3ead49a0545d3dd9b3f119 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => '요청한 페이지에 권한이 없습니다.',
     'permissionJson' => '요청한 작업을 수행 할 권한이 없습니다.',
@@ -29,6 +27,7 @@ return [
     'social_account_register_instructions' => '아직 계정이없는 경우 :socialAccount 옵션을 사용하여 계정을 등록 할 수 있습니다.',
     'social_driver_not_found' => '소셜 드라이버를 찾을 수 없음',
     'social_driver_not_configured' => '귀하의 :socialAccount 소셜 설정이 올바르게 구성되지 않았습니다.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => '파일 경로 :filePath에 업로드 할 수 없습니다. 서버에 쓰기 기능이 활성화 되어있는지 확인하세요.',
@@ -66,6 +65,7 @@ return [
     'role_cannot_be_edited' => '역할을 수정할 수 없습니다.',
     'role_system_cannot_be_deleted' => '이 역할은 시스템 역할입니다. 삭제할 수 없습니다.',
     'role_registration_default_cannot_delete' => '이 역할은 기본 등록 역할로 설정되어있는 동안 삭제할 수 없습니다.',
+    'role_cannot_remove_only_admin' => 'This user is the only user assigned to the administrator role. Assign the administrator role to another user before attempting to remove it here.',
 
     // Comments
     'comment_list' => '댓글을 가져 오는 중에 오류가 발생했습니다.',
@@ -81,4 +81,5 @@ return [
     'error_occurred' => '오류가 발생하였습니다.',
     'app_down' => ':appName가 다운되었습니다.',
     'back_soon' => '곧 복구될 예정입니다.',
+
 ];
diff --git a/resources/lang/ko/pagination.php b/resources/lang/ko/pagination.php
new file mode 100644 (file)
index 0000000..9a06aed
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
+return [
+
+    'previous' => '&laquo; 이전',
+    'next'     => '다음 &raquo;',
+
+];
diff --git a/resources/lang/ko/passwords.php b/resources/lang/ko/passwords.php
new file mode 100644 (file)
index 0000000..26790e9
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
+return [
+
+    'password' => '비밀번호는 6자 이상이어야 하며 확인과 일치해야 합니다.',
+    'user' => "해당 이메일 주소의 사용자가 없습니다.",
+    'token' => '해당 비밀번호의 초기화 토큰이 만료되었습니다.',
+    'sent' => '페스워드 초기화 링크를 메일로 보냈습니다!',
+    'reset' => '비밀번호가 초기화 되었습니다!',
+
+];
similarity index 68%
rename from resources/lang/kr/settings.php
rename to resources/lang/ko/settings.php
index 61b315cabeb24744a163039a1e9011412c40e292..346e7f6a8462d08ea8adc38b91b72f33d8f47770 100755 (executable)
@@ -1,32 +1,35 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
-
+    // Common Messages
     'settings' => '설정',
     'settings_save' => '설정 저장',
     'settings_save_success' => '설정이 저장되었습니다.',
 
-    /**
-     * App settings
-     */
-
-    'app_settings' => '앱 설정',
+    // App Settings
+    'app_customization' => 'Customization',
+    'app_features_security' => 'Features & Security',
     'app_name' => '어플리케이션 이름',
     'app_name_desc' => '해당 이름은 헤더와 모든 이메일에 표시됩니다.',
     'app_name_header' => '헤더에 어플리케이션 이름을 표시하시겠습니까?',
+    'app_public_access' => 'Public Access',
+    'app_public_access_desc' => 'Enabling this option will allow visitors, that are not logged-in, to access content in your BookStack instance.',
+    'app_public_access_desc_guest' => 'Access for public visitors can be controlled through the "Guest" user.',
+    'app_public_access_toggle' => 'Allow public access',
     'app_public_viewing' => '공개 보기를 허용하시겠습니까?',
     'app_secure_images' => '더 높은 보안 이미지 업로드를 사용하시겠습니까?',
+    'app_secure_images_toggle' => 'Enable higher security image uploads',
     'app_secure_images_desc' => '성능상의 이유로 모든 이미지를 공개합니다. 해당 옵션은 이미지 URL 앞에 추측하기 어려운 임의의 문자열을 추가합니다. 간편한 접근을 방지하기 위해 디렉토리 색인을 비활성화하십시오.',
     'app_editor' => '페이지 에디터',
     'app_editor_desc' => '모든 사용자가 페이지를 편집하는데 사용할 에디터를 선택하십시오.',
     'app_custom_html' => '사용자 정의 HTML 헤드 컨텐츠',
     'app_custom_html_desc' => '여기에 추가된 모든 내용은 모든 페이지의 <head> 섹션 아래쪽에 삽입됩니다. 이는 스타일 오버라이딩이나 분석 코드 삽입에 편리합니다.',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => '어플리케이션 로고',
     'app_logo_desc' => '해당 이미지는 반드시 높이가 43픽셀이어야 합니다. <br>대용량 이미지는 축소됩니다.',
     'app_primary_color' => '어플리케이션 기본 색상',
@@ -35,22 +38,23 @@ return [
     'app_homepage_desc' => '기본 화면 대신에 홈페이지에 표시할 화면을 선택하십시오. 선택된 페이지에서는 페이지 권한이 무시됩니다.',
     'app_homepage_select' => '페이지를 선택하십시오',
     'app_disable_comments' => '주석 비활성화',
+    'app_disable_comments_toggle' => 'Disable comments',
     'app_disable_comments_desc' => '어플리케이션의 모든 페이지에서 주석을 비활성화합니다. 기존의 주석은 표시되지 않습니다.',
-    /**
-     * Registration settings
-     */
+
+    // Registration Settings
     'reg_settings' => '등록 설정',
-    'reg_allow' => '등록을 허가하시겠습니까?',
+    'reg_enable' => 'Enable Registration',
+    'reg_enable_toggle' => 'Enable registration',
+    'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
     'reg_default_role' => '등록 후 기본 사용자 역할',
-    'reg_confirm_email' => '이메일 확인을 요구하시겠습니까?',
+    'reg_email_confirmation' => 'Email Confirmation',
+    'reg_email_confirmation_toggle' => 'Require email confirmation',
     'reg_confirm_email_desc' => '도메인 제한이 사용되면 이메일 확인이 요구되며, 하단의 값은 무시됩니다.',
     'reg_confirm_restrict_domain' => '도메인 등록 제한',
     'reg_confirm_restrict_domain_desc' => '등록을 제한할 이메일 도메인의 목록을 쉼표로 구분하여 입력해주십시오. 사용자는 어플리케이션과의 상호작용을 허가받기 전에 이메일 주소를 확인하는 이메일을 받게 됩니다, <br> 등록이 완료된 후에는 이메일 주소를 변경할 수 있습니다.',
     'reg_confirm_restrict_domain_placeholder' => '제한 없음 설정',
-    /**
-     * Maintenance settings
-     */
 
+    // Maintenance settings
     'maint' => 'Maintenance',
     'maint_image_cleanup' => '이미지 정리',
     'maint_image_cleanup_desc' => "페이지를 스캔하여 현재 사용중인 이미지와 도면에서 수정된 내용 및 중복된 이미지를 확인합니다. 이를 실행하기 전에 전체 데이터베이스와 이미지의 백업을 작성했는지 확인하십시오.",
@@ -59,9 +63,8 @@ return [
     'maint_image_cleanup_warning' => '잠재적으로 사용되지 않는 이미지를 찾았습니다. 해당 이미지들을 삭제하시겠습니까?',
     'maint_image_cleanup_success' => ':잠재적으로 사용되지 않는 이미지들이 삭제되었습니다.',
     'maint_image_cleanup_nothing_found' => '사용되지 않는 이미지를 찾을 수 없습니다. 아무것도 삭제되지 않았습니다.',
-    /**
-     * Role settings
-     */
+
+    // Role Settings
     'roles' => '역할',
     'role_user_roles' => '사용자 역할',
     'role_create' => '신규 역할 생성',
@@ -82,6 +85,7 @@ return [
     'role_manage_roles' => '역할 및 역할 권한 관리',
     'role_manage_entity_permissions' => '모든 책, 챕터, 페이지 관리',
     'role_manage_own_entity_permissions' => '보유한 책, 챕터, 페이지에 대한 권한 관리',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => '어플리케이선 설정 관리',
     'role_asset' => '자산 관리',
     'role_asset_desc' => '해당 권한들은 시스템 내의 Assets 파일에 대한 기본적인 접근을 제어합니다.',
@@ -93,15 +97,23 @@ return [
     'role_update_success' => '역할이 업데이트되었습니다.',
     'role_users' => '해당 역할의 사용자',
     'role_users_none' => '현재 이 역할에 할당된 사용자가 없습니다.',
-    /**
-     * Users
-     */
+
+    // Users
     'users' => '사용자',
     'user_profile' => '사용자 프로필',
     'users_add_new' => '사용자 추가',
     'users_search' => '사용자 검색',
+    'users_details' => 'User Details',
+    'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.',
+    'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.',
     'users_role' => '사용자 역할',
+    'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
+    'users_password' => 'User Password',
+    'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 6 characters long.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => '외부 인증 ID',
+    'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your LDAP system.',
     'users_password_warning' => '비밀번호를 변경하시려면 다음을 입력하십시오:',
     'users_system_public' => '이 사용자는 당신의 인스턴스를 방문하는 게스트 사용자를 나타냅니다. 로그인하는 데는 사용할 수 없지만 자동으로 할당됩니다.',
     'users_delete' => '사용자 삭제',
@@ -115,10 +127,40 @@ return [
     'users_avatar' => '사용자 아바타',
     'users_avatar_desc' => '해당 이미지는 256픽셀의 정사각형 이미지여야합니다.',
     'users_preferred_language' => '선호하는 언어',
+    'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
     'users_social_accounts' => '소셜 계정',
     'users_social_accounts_info' => '여기에서 다른 계정을 연결하여 더 빠르고 쉽게 로그인할 수 있습니다. 여기에서 계정 연결을 해제하면 이전에 승인된 접근이 제공되지 않습니다 연결된 소셜 계정의 프로필 설정에서 접근 권한을 취소하십시오.',
     'users_social_connect' => '계정 연결',
     'users_social_disconnect' => '계정 연결 해제',
     'users_social_connected' => ':socialAccount 계정이 당신의 프로필에 연결되었습니다.',
     'users_social_disconnected' => ':socialAccount 계정이 당신의 프로필에서 연결해제되었습니다.',
-];
\ No newline at end of file
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
+];
similarity index 66%
rename from resources/lang/kr/validation.php
rename to resources/lang/ko/validation.php
index aa2916ae448c954d3c0923eaea20385d163bc642..07ecdd021e4127cb52cbb819b96d8beee6b48bf9 100644 (file)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => ':attribute가 반드시 허용되어야 합니다.',
     'active_url'           => ':attribute가 올바른 URL이 아닙니다.',
     'after'                => ':attribute는 :date이후 날짜여야 합니다.',
@@ -35,12 +30,41 @@ return [
     'digits'               => ':attribute 는 반드시 :digits 숫자(digit)여야 합니다.',
     'digits_between'       => ':attribute 는 반드시 :min이상 :max이하 숫자여야 합니다.',
     'email'                => ':attribute 는 반드시 이메일 이어야 합니다.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => ':attribute 항목이 꼭 필요합니다.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => '선택된 :attribute 은(는) 사용 불가합니다.',
     'image'                => ':attribute 는 반드시 이미지여야 합니다.',
+    'image_extension'      => 'The :attribute must have a valid & supported image extension.',
     'in'                   => '선택된 :attribute 은(는) 사용 불가합니다.',
     'integer'              => ':attribute 는 반드시(integer)여야 합니다.',
     'ip'                   => ':attribute 는 반드시 IP주소 여야 합니다.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => ':attribute :max 보다 크면 안됩니다.',
         'file'    => ':attribute :max kilobytes보다 크면 안됩니다.',
@@ -54,7 +78,9 @@ return [
         'string'  => ':attribute 은(는) 최소한 :min 개 문자여야 합니다.',
         'array'   => ':attribute 은(는) 적어도 :min 개의 항목이어야 합니다.',
     ],
+    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => '선택된 :attribute 는 사용할 수 없습니다',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attribute 반드시 숫자여야 합니다.',
     'regex'                => ':attribute 포멧이 잘못되었습니다.',
     'required'             => ':attribute 항목은 필수입니다..',
@@ -74,35 +100,15 @@ return [
     'timezone'             => ':attribute 정상적인 지역(zone)이어야 합니다.',
     'unique'               => ':attribute 은(는) 이미 사용중입니다..',
     'url'                  => ':attribute 포멧이 사용 불가합니다.',
+    'uploaded'             => 'The file could not be uploaded. The server may not accept files of this size.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => '비밀번호 확인이 필요합니다.',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
diff --git a/resources/lang/kr/pagination.php b/resources/lang/kr/pagination.php
deleted file mode 100644 (file)
index d4a9eef..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-
-return [
-
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
-    'previous' => '&laquo; 이전',
-    'next'     => '다음 &raquo;',
-
-];
diff --git a/resources/lang/kr/passwords.php b/resources/lang/kr/passwords.php
deleted file mode 100644 (file)
index de49b1f..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-return [
-
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
-    'password' => '비밀번호는 6자 이상이어야 하며 확인과 일치해야 합니다.',
-    'user' => "해당 이메일 주소의 사용자가 없습니다.",
-    'token' => '해당 비밀번호의 초기화 토큰이 만료되었습니다.',
-    'sent' => '페스워드 초기화 링크를 메일로 보냈습니다!',
-    'reset' => '비밀번호가 초기화 되었습니다!',
-
-];
index 021b6d21e29d4b0d7db8bf096af4f168c66e2383..76272888119a3127329f3cf986a49514bdb016be 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => 'maakte pagina',
     'page_create_notification'    => 'Pagina Succesvol Aangemaakt',
@@ -36,9 +34,9 @@ return [
     'book_delete_notification'    => 'Boek Succesvol Verwijderd',
     'book_sort'                   => 'sorteerde boek',
     'book_sort_notification'      => 'Boek Succesvol Gesorteerd',
-    
+
     // Bookshelves
-    'bookshelf_create'                 => 'maakte Boekenplank',
+    'bookshelf_create'            => 'maakte Boekenplank',
     'bookshelf_create_notification'    => 'Boekenplank Succesvol Aangemaakt',
     'bookshelf_update'                 => 'veranderde boekenplank',
     'bookshelf_update_notification'    => 'Boekenplank Succesvol Bijgewerkt',
@@ -46,5 +44,5 @@ return [
     'bookshelf_delete_notification'    => 'Boekenplank Succesvol Verwijderd',
 
     // Other
-    'commented_on'                => 'reactie op',                                                                                             
+    'commented_on'                => 'reactie op',
 ];
index 31bd330cc3d5ae28acb9663883cfce9f775d3800..059214bb477dc12f6db157578105f0fb1774bc04 100644 (file)
@@ -1,21 +1,15 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => 'Deze inloggegevens zijn niet bij ons bekend.',
     'throttle' => 'Te veel loginpogingen! Probeer het opnieuw na :seconds seconden.',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => 'Registreren',
     'log_in' => 'Log in',
     'log_in_with' => 'Login met :socialDriver',
@@ -27,11 +21,13 @@ return [
     'email' => 'Email',
     'password' => 'Wachtwoord',
     'password_confirm' => 'Wachtwoord Bevestigen',
-    'password_hint' => 'Minimaal 6 tekens',
+    'password_hint' => 'Minimaal 8 tekens',
     'forgot_password' => 'Wachtwoord vergeten?',
     'remember_me' => 'Mij onthouden',
     'ldap_email_hint' => 'Geef een email op waarmee je dit account wilt gebruiken.',
     'create_account' => 'Account Aanmaken',
+    'already_have_account' => 'Already have an account?',
+    'dont_have_account' => 'Don\'t have an account?',
     'social_login' => 'Social Login',
     'social_registration' => 'Social Registratie',
     'social_registration_text' => 'Registreer en log in met een andere dienst.',
@@ -43,23 +39,18 @@ return [
     'register_success' => 'Bedankt voor het inloggen. Je bent ook geregistreerd.',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => 'Wachtwoord Herstellen',
     'reset_password_send_instructions' => 'Geef je e-mail en we sturen je een link om je wachtwoord te herstellen',
     'reset_password_send_button' => 'Link Sturen',
     'reset_password_sent_success' => 'Een link om je wachtwoord te herstellen is verstuurd naar :email.',
     'reset_password_success' => 'Je wachtwoord is succesvol hersteld.',
-
     'email_reset_subject' => 'Herstel je wachtwoord van :appName',
     'email_reset_text' => 'Je ontvangt deze e-mail zodat je je wachtwoord kunt herstellen.',
     'email_reset_not_requested' => 'Als je jouw wachtwoord niet wilt wijzigen, doe dan niets.',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => 'Bevestig je e-mailadres op :appName',
     'email_confirm_greeting' => 'Bedankt voor je aanmelding op :appName!',
     'email_confirm_text' => 'Bevestig je registratie door op onderstaande knop te drukken:',
@@ -73,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Klik op de link in de e-mail die vlak na je registratie is verstuurd.',
     'email_not_confirmed_resend' => 'Als je deze e-mail niet kunt vinden kun je deze met onderstaande formulier opnieuw verzenden.',
     'email_not_confirmed_resend_button' => 'Bevestigingsmail Opnieuw Verzenden',
-];
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
+];
\ No newline at end of file
index d44bd514d22faf1dfec93b78556d6ec7e68b4835..87d5935a67b64cd105a512a0aa19273847801dee 100644 (file)
@@ -1,7 +1,9 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    
     // Buttons
     'cancel' => 'Annuleren',
     'confirm' => 'Bevestigen',
@@ -11,7 +13,7 @@ return [
     'select' => 'Kies',
     'toggle_all' => 'Toggle Alles',
     'more' => 'Meer',
-    
+
     // Form Labels
     'name' => 'Naam',
     'description' => 'Beschrijving',
@@ -38,10 +40,14 @@ return [
     'add' => 'Toevoegen',
 
     // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
     'sort_name' => 'Naam',
     'sort_created_at' => 'Aanmaakdatum',
     'sort_updated_at' => 'Gewijzigd op',
-    
+
     // Misc
     'deleted_user' => 'Verwijderde gebruiker',
     'no_activity' => 'Geen activiteiten',
@@ -53,20 +59,18 @@ return [
     'grid_view' => 'Grid weergave',
     'list_view' => 'Lijst weergave',
     'default' => 'Standaard',
+    'breadcrumb' => 'Breadcrumb',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => 'Profiel Weergeven',
     'edit_profile' => 'Profiel Bewerken',
 
     // Layout tabs
     'tab_info' => 'Info',
     'tab_content' => 'Inhoud',
-    
-    /**
-     * Email Content
-     */
+
+    // Email Content
     'email_action_help' => 'Als je de knop ":actionText" niet werkt, kopieer en plak de onderstaande URL in je web browser:',
     'email_rights' => 'Alle rechten voorbehouden',
 ];
index 576298ef2b2211fcc83f5436469b252438ac2f60..ffff70b08fd2430834b0343abd3f7138c5331aaa 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => 'Selecteer Afbeelding',
     'image_all' => 'Alles',
     'image_all_title' => 'Alle afbeeldingen weergeven',
@@ -22,9 +23,9 @@ return [
     'image_upload_success' => 'Afbeelding succesvol geüpload',
     'image_update_success' => 'Afbeeldingsdetails succesvol verwijderd',
     'image_delete_success' => 'Afbeelding succesvol verwijderd',
-    /**
-     * Code editor
-     */
+    'image_upload_remove' => 'Remove',
+
+    // Code Editor
     'code_editor' => 'Code invoegen',
     'code_language' => 'Code taal',
     'code_content' => 'Code',
index 34142ad7f407c7fb4118e5aab84767b4b77e737b..bcd0690538174fe48b975289659761ad5d48897a 100644 (file)
@@ -1,14 +1,17 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => 'Recent Aangemaakt',
     'recently_created_pages' => 'Recent Aangemaakte Pagina\'s',
     'recently_updated_pages' => 'Recent Bijgewerkte Pagina\'s',
     'recently_created_chapters' => 'Recent Aangemaakte Hoofdstukken',
     'recently_created_books' => 'Recent Aangemaakte Boeken',
+    'recently_created_shelves' => 'Recently Created Shelves',
     'recently_update' => 'Recent Bijgewerkt',
     'recently_viewed' => 'Recent Bekeken',
     'recent_activity' => 'Recente Activiteit',
@@ -19,7 +22,6 @@ return [
     'meta_created_name' => 'Aangemaakt: :timeLength door :user',
     'meta_updated' => ':timeLength Aangepast',
     'meta_updated_name' => 'Aangepast: :timeLength door :user',
-    'x_pages' => ':count Pagina\'s',
     'entity_select' => 'Entiteit Selecteren',
     'images' => 'Afbeeldingen',
     'my_recent_drafts' => 'Mijn Concepten',
@@ -32,17 +34,13 @@ return [
     'export_pdf' => 'PDF File',
     'export_text' => 'Plain Text File',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => 'Permissies',
     'permissions_intro' => 'Als je dit aanzet, dan gelden rol-permissies niet meer voor deze pagina.',
     'permissions_enable' => 'Custom Permissies Aanzetten',
     'permissions_save' => 'Permissies Opslaan',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => 'Zoekresultaten',
     'search_total_results_found' => ':count resultaten gevonden|:count resultaten gevonden',
     'search_clear' => 'Zoekopdracht wissen',
@@ -53,11 +51,13 @@ return [
     'search_content_type' => 'Content Type',
     'search_exact_matches' => 'Exacte Matches',
     'search_tags' => 'Zoek tags',
+    'search_options' => 'Options',
     'search_viewed_by_me' => 'Bekeken door mij',
     'search_not_viewed_by_me' => 'Niet bekeken door mij',
     'search_permissions_set' => 'Permissies gezet',
     'search_created_by_me' => 'Door mij gemaakt',
     'search_updated_by_me' => 'Door mij geupdate',
+    'search_date_options' => 'Date Options',
     'search_updated_before' => 'Geupdate voor',
     'search_updated_after' => 'Geupdate na',
     'search_created_before' => 'Gecreeerd voor',
@@ -74,6 +74,7 @@ return [
     'shelves_create' => 'Nieuwe Boekenplank Aanmaken',
     'shelves_popular' => 'Populaire Boekenplanken',
     'shelves_new' => 'Nieuwe Boekenplanken',
+    'shelves_new_action' => 'New Shelf',
     'shelves_popular_empty' => 'De meest populaire boekenplanken worden hier weergegeven.',
     'shelves_new_empty' => 'De meest recent aangemaakt boekenplanken worden hier weergeven.',
     'shelves_save' => 'Boekenplanken Opslaan',
@@ -95,7 +96,7 @@ return [
     'shelves_copy_permissions' => 'Kopieer Permissies',
     'shelves_copy_permissions_explain' => 'Met deze actie worden de permissies van deze boekenplank gekopieerd naar alle boeken op de plank. Voordat deze actie wordt uitgevoerd, zorg dat de wijzigingen in de permissies van deze boekenplank zijn opgeslagen.',
     'shelves_copy_permission_success' => 'Boekenplank permissies gekopieerd naar :count boeken',
-       
+
     // Books
     'book' => 'Boek',
     'books' => 'Boeken',
@@ -104,7 +105,9 @@ return [
     'books_popular' => 'Populaire Boeken',
     'books_recent' => 'Recente Boeken',
     'books_new' => 'Nieuwe Boeken',
+    'books_new_action' => 'New Book',
     'books_popular_empty' => 'De meest populaire boeken worden hier weergegeven.',
+    'books_new_empty' => 'The most recently created books will appear here.',
     'books_create' => 'Nieuw Boek Aanmaken',
     'books_delete' => 'Boek Verwijderen',
     'books_delete_named' => 'Verwijder Boek :bookName',
@@ -118,7 +121,6 @@ return [
     'books_permissions_updated' => 'Boek Permissies Opgeslagen',
     'books_empty_contents' => 'Er zijn nog een hoofdstukken en pagina\'s voor dit boek gemaakt.',
     'books_empty_create_page' => 'Pagina Toevoegen',
-    'books_empty_or' => 'of',
     'books_empty_sort_current_book' => 'Boek sorteren',
     'books_empty_add_chapter' => 'Hoofdstuk Toevoegen',
     'books_permissions_active' => 'Boek Permissies Actief',
@@ -126,12 +128,15 @@ return [
     'books_navigation' => 'Boek Navigatie',
     'books_sort' => 'Inhoud van het boek sorteren',
     'books_sort_named' => 'Sorteer Boek :bookName',
+    'books_sort_name' => 'Sort by Name',
+    'books_sort_created' => 'Sort by Created Date',
+    'books_sort_updated' => 'Sort by Updated Date',
+    'books_sort_chapters_first' => 'Chapters First',
+    'books_sort_chapters_last' => 'Chapters Last',
     'books_sort_show_other' => 'Bekijk Andere Boeken',
     'books_sort_save' => 'Nieuwe Order Opslaan',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => 'Hoofdstuk',
     'chapters' => 'Hoofdstukken',
     'x_chapters' => ':count Hoofdstuk|:count Hoofdstukken',
@@ -155,9 +160,7 @@ return [
     'chapters_permissions_success' => 'Hoofdstuk Permissies Bijgewerkt',
     'chapters_search_this' => 'Doorzoek dit hoofdstuk',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => 'Pagina',
     'pages' => 'Pagina\'s',
     'x_pages' => ':count Pagina|:count Pagina\'s',
@@ -174,7 +177,7 @@ return [
     'pages_delete_confirm' => 'Weet je zeker dat je deze pagina wilt verwijderen?',
     'pages_delete_draft_confirm' => 'Weet je zeker dat je dit concept wilt verwijderen?',
     'pages_editing_named' => 'Pagina :pageName Bewerken',
-    'pages_edit_toggle_header' => 'Wissel header',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'Concept opslaan',
     'pages_edit_draft' => 'Paginaconcept Bewerken',
     'pages_editing_draft' => 'Concept Bewerken',
@@ -192,9 +195,13 @@ return [
     'pages_md_preview' => 'Voorbeeld',
     'pages_md_insert_image' => 'Afbeelding Invoegen',
     'pages_md_insert_link' => 'Entity Link Invoegen',
+    'pages_md_insert_drawing' => 'Insert Drawing',
     'pages_not_in_chapter' => 'Deze pagina staat niet in een hoofdstuk',
     'pages_move' => 'Pagina Verplaatsten',
     'pages_move_success' => 'Pagina verplaatst naar ":parentName"',
+    'pages_copy' => 'Copy Page',
+    'pages_copy_desination' => 'Copy Destination',
+    'pages_copy_success' => 'Page successfully copied',
     'pages_permissions' => 'Pagina Permissies',
     'pages_permissions_success' => 'Pagina Permissies bijgwerkt',
     'pages_revision' => 'Revisie',
@@ -204,6 +211,8 @@ return [
     'pages_revisions_created_by' => 'Aangemaakt door',
     'pages_revisions_date' => 'Revisiedatum',
     'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
     'pages_revisions_changelog' => 'Changelog',
     'pages_revisions_changes' => 'Wijzigingen',
     'pages_revisions_current' => 'Huidige Versie',
@@ -225,16 +234,21 @@ return [
         'message' => ':start :time. Take care not to overwrite each other\'s updates!',
     ],
     'pages_draft_discarded' => 'Draft discarded, The editor has been updated with the current page content',
+    'pages_specific' => 'Specific Page',
+    'pages_is_template' => 'Page Template',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => 'Pagina Labels',
+    'chapter_tags' => 'Chapter Tags',
+    'book_tags' => 'Book Tags',
+    'shelf_tags' => 'Shelf Tags',
     'tag' => 'Label',
-    'tags' =>  '',
+    'tags' =>  'Tags',
+    'tag_name' =>  'Tag Name',
     'tag_value' => 'Label Waarde (Optioneel)',
     'tags_explain' => "Voeg labels toe om de inhoud te categoriseren. \n Je kunt meerdere labels toevoegen.",
     'tags_add' => 'Voeg een extra label toe',
+    'tags_remove' => 'Remove this tag',
     'attachments' => 'Bijlages',
     'attachments_explain' => 'Upload bijlages of voeg een link toe. Deze worden zichtbaar in het navigatiepaneel.',
     'attachments_explain_instant_save' => 'Wijzigingen worden meteen opgeslagen.',
@@ -260,21 +274,25 @@ return [
     'attachments_file_uploaded' => 'Bestand succesvol geüpload',
     'attachments_file_updated' => 'Bestand succesvol bijgewerkt',
     'attachments_link_attached' => 'Link successfully gekoppeld aan de pagina',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => 'Lid sinds :time',
     'profile_created_content' => 'Aangemaakte Inhoud',
     'profile_not_created_pages' => ':userName heeft geen pagina\'s gemaakt',
     'profile_not_created_chapters' => ':userName heeft geen hoofdstukken gemaakt',
     'profile_not_created_books' => ':userName heeft geen boeken gemaakt',
+    'profile_not_created_shelves' => ':userName has not created any shelves',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => 'Reactie',
     'comments' => 'Reacties',
+    'comment_add' => 'Add Comment',
     'comment_placeholder' => 'Laat hier een reactie achter',
     'comment_count' => '{0} Geen reacties|{1} 1 Reactie|[2,*] :count Reacties',
     'comment_save' => 'Sla reactie op',
@@ -289,10 +307,9 @@ return [
     'comment_delete_confirm' => 'Zeker reactie verwijderen?',
     'comment_in_reply_to' => 'Antwoord op :commentId',
 
-     /**
-     * Revision
-     */
+    // Revision
     'revision_delete_confirm' => 'Weet u zeker dat u deze revisie wilt verwijderen?',
+    'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
     'revision_delete_success' => 'Revisie verwijderd',
     'revision_cannot_delete_latest' => 'Kan de laatste revisie niet verwijderen.'
-];
+];
\ No newline at end of file
index 19dbbd8d10db59ec824383ea02fafcfba953494c..f83f56a46c2ea3c726029075d82aff6990c756ad 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => 'Je hebt onvoldoende rechten om deze pagina te zien.',
     'permissionJson' => 'Je hebt onvoldoende rechten voor deze actie.',
@@ -20,6 +18,7 @@ return [
     'ldap_extension_not_installed' => 'LDAP PHP extension not installed',
     'ldap_cannot_connect' => 'Kon niet met de LDAP server verbinden',
     'social_no_action_defined' => 'Geen actie gedefineerd',
+    'social_login_bad_response' => "Error received during :socialAccount login: \n:error",
     'social_account_in_use' => 'Dit :socialAccount account is al in gebruik, Probeer in te loggen met de :socialAccount optie.',
     'social_account_email_in_use' => 'Het e-mailadres :email is al in gebruik. Als je al een account hebt kun je een :socialAccount account verbinden met je profielinstellingen.',
     'social_account_existing' => 'Dit :socialAccount is al gekoppeld aan een profiel.',
@@ -28,23 +27,29 @@ return [
     'social_account_register_instructions' => 'Als je nog geen account hebt kun je je registreren met de :socialAccount optie.',
     'social_driver_not_found' => 'Social driver niet gevonden',
     'social_driver_not_configured' => 'Je :socialAccount instellingen zijn correct geconfigureerd.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => 'Bestand :filePath kon niet geupload worden. Zorg dat je schrijfrechten op de server hebt.',
     'cannot_get_image_from_url' => 'Kon geen afbeelding genereren van :url',
     'cannot_create_thumbs' => 'De server kon geen thumbnails maken. Controleer of je de GD PHP extensie geïnstalleerd hebt.',
     'server_upload_limit' => 'Het afbeeldingsformaat is te groot. Probeer een kleinere bestandsgrootte.',
+    'uploaded'  => 'The server does not allow uploads of this size. Please try a smaller file size.',
     'image_upload_error' => 'Er ging iets fout bij het uploaden van de afbeelding',
+    'image_upload_type_error' => 'The image type being uploaded is invalid',
     'file_upload_timeout' => 'Het uploaden van het bestand is verlopen.',
 
     // Attachments
     'attachment_page_mismatch' => 'Bij het bijwerken van de bijlage bleek de pagina onjuist',
+    'attachment_not_found' => 'Attachment not found',
 
     // Pages
     'page_draft_autosave_fail' => 'Kon het concept niet opslaan. Zorg ervoor dat je een werkende internetverbinding hebt.',
+    'page_custom_home_deletion' => 'Cannot delete a page while it is set as a homepage',
 
     // Entities
     'entity_not_found' => 'Entiteit niet gevonden',
+    'bookshelf_not_found' => 'Bookshelf not found',
     'book_not_found' => 'Boek niet gevonden',
     'page_not_found' => 'Pagina niet gevonden',
     'chapter_not_found' => 'Hoofdstuk niet gevonden',
@@ -60,6 +65,7 @@ return [
     'role_cannot_be_edited' => 'Deze rol kan niet bewerkt worden',
     'role_system_cannot_be_deleted' => 'Dit is een systeemrol en kan niet verwijderd worden',
     'role_registration_default_cannot_delete' => 'Deze rol kan niet verwijerd worden zolang dit de standaardrol na registratie is.',
+    'role_cannot_remove_only_admin' => 'This user is the only user assigned to the administrator role. Assign the administrator role to another user before attempting to remove it here.',
 
     // Comments
     'comment_list' => 'Er is een fout opgetreden tijdens het ophalen van de reacties.',
@@ -67,6 +73,7 @@ return [
     'comment_add' => 'Er is een fout opgetreden tijdens het toevoegen van de reactie.',
     'comment_delete' => 'Er is een fout opgetreden tijdens het verwijderen van de reactie.',
     'empty_comment' => 'Kan geen lege reactie toevoegen.',
+
     // Error pages
     '404_page_not_found' => 'Pagina Niet Gevonden',
     'sorry_page_not_found' => 'Sorry, de pagina die je zocht is niet beschikbaar.',
@@ -74,4 +81,5 @@ return [
     'error_occurred' => 'Er Ging Iets Fout',
     'app_down' => ':appName is nu niet beschikbaar',
     'back_soon' => 'Komt snel weer online.',
-];
\ No newline at end of file
+
+];
index 9a2a9677a4fd63b1dedb87f862e20d674a2eeb44..a5762a5ae1f5bd17a2884717b376a7bd892f0f3b 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; Vorige',
     'next'     => 'Volgende &raquo;',
 
index f89830804b5c8d78a2bfb4e6453e50cd8b817b2a..a1efd480e488f197d8234e23a43fc600ff49466e 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => 'Wachtwoorden moeten overeenkomen en minimaal zes tekens lang zijn.',
     'user' => "We kunnen niemand vinden met dat e-mailadres.",
     'token' => 'De token om het wachtwoord te herstellen is ongeldig.',
index ba73dc7e9d250fcb1a2aa5e0acfd3604df55db56..dc5521797a90f997498bea516471457fbc12757d 100644 (file)
@@ -1,56 +1,70 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
-
+    // Common Messages
     'settings' => 'Instellingen',
     'settings_save' => 'Instellingen Opslaan',
     'settings_save_success' => 'Instellingen Opgeslagen',
 
-    /**
-     * App settings
-     */
-
-    'app_settings' => 'App Instellingen',
+    // App Settings
+    'app_customization' => 'Customization',
+    'app_features_security' => 'Features & Security',
     'app_name' => 'Applicatienaam',
     'app_name_desc' => 'De applicatienaam wordt in e-mails in in de header weergegeven.',
     'app_name_header' => 'Applicatienaam in de header weergeven?',
+    'app_public_access' => 'Public Access',
+    'app_public_access_desc' => 'Enabling this option will allow visitors, that are not logged-in, to access content in your BookStack instance.',
+    'app_public_access_desc_guest' => 'Access for public visitors can be controlled through the "Guest" user.',
+    'app_public_access_toggle' => 'Allow public access',
     'app_public_viewing' => 'Publieke bewerkingen toestaan?',
     'app_secure_images' => 'Beter beveiligide afbeeldingen gebruiken?',
+    'app_secure_images_toggle' => 'Enable higher security image uploads',
     'app_secure_images_desc' => 'Omwille van de performance zijn alle afbeeldingen publiek toegankelijk. Zorg ervoor dat je de \'directory index\' niet hebt ingeschakeld.',
     'app_editor' => 'Pagina Bewerken',
     'app_editor_desc' => 'Selecteer welke tekstverwerker je wilt gebruiken.',
     'app_custom_html' => 'Speciale HTML toevoegen',
     'app_custom_html_desc' => 'Alles wat je hier toevoegd wordt in de <head> sectie van elke pagina meengenomen. Dit kun je bijvoorbeeld voor analytics gebruiken.',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'Applicatielogo',
     'app_logo_desc' => 'De afbeelding moet 43px hoog zijn. <br>Grotere afbeeldingen worden geschaald.',
     'app_primary_color' => 'Applicatie hoofdkleur',
     'app_primary_color_desc' => 'Geef een hexadecimale waarde. <br>Als je niks invult wordt de standaardkleur gebruikt.',
+    'app_homepage' => 'Application Homepage',
+    'app_homepage_desc' => 'Select a view to show on the homepage instead of the default view. Page permissions are ignored for selected pages.',
+    'app_homepage_select' => 'Select a page',
     'app_disable_comments' => 'Reacties uitschakelen',
+    'app_disable_comments_toggle' => 'Disable comments',
     'app_disable_comments_desc' => 'Schakel opmerkingen uit op alle pagina\'s in de applicatie. Bestaande opmerkingen worden niet getoond.',
 
-    /**
-     * Registration settings
-     */
-
+    // Registration Settings
     'reg_settings' => 'Registratieinstellingen',
-    'reg_allow' => 'Registratie toestaan?',
+    'reg_enable' => 'Enable Registration',
+    'reg_enable_toggle' => 'Enable registration',
+    'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
     'reg_default_role' => 'Standaard rol na registratie',
-    'reg_confirm_email' => 'E-mailbevesting vereist?',
+    'reg_email_confirmation' => 'Email Confirmation',
+    'reg_email_confirmation_toggle' => 'Require email confirmation',
     'reg_confirm_email_desc' => 'Als domeinrestricties aan staan dan is altijd e-maibevestiging nodig. Onderstaande instelling wordt dan genegeerd.',
     'reg_confirm_restrict_domain' => 'Beperk registratie tot een maildomein',
     'reg_confirm_restrict_domain_desc' => 'Geen een komma-gescheiden lijst van domeinnamen die gebruikt mogen worden bij registratie. <br> Let op: na registratie kunnen gebruikers hun e-mailadres nog steeds wijzigen.',
     'reg_confirm_restrict_domain_placeholder' => 'Geen beperkingen ingesteld',
 
-    /**
-     * Role settings
-     */
+    // Maintenance settings
+    'maint' => 'Maintenance',
+    'maint_image_cleanup' => 'Cleanup Images',
+    'maint_image_cleanup_desc' => "Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.",
+    'maint_image_cleanup_ignore_revisions' => 'Ignore images in revisions',
+    'maint_image_cleanup_run' => 'Run Cleanup',
+    'maint_image_cleanup_warning' => ':count potentially unused images were found. Are you sure you want to delete these images?',
+    'maint_image_cleanup_success' => ':count potentially unused images found and deleted!',
+    'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!',
 
+    // Role Settings
     'roles' => 'Rollen',
     'role_user_roles' => 'Gebruikrollen',
     'role_create' => 'Nieuwe Rol Maken',
@@ -65,14 +79,17 @@ return [
     'role_details' => 'Rol Details',
     'role_name' => 'Rolnaam',
     'role_desc' => 'Korte beschrijving van de rol',
+    'role_external_auth_id' => 'External Authentication IDs',
     'role_system' => 'Systeem Permissies',
     'role_manage_users' => 'Gebruikers beheren',
     'role_manage_roles' => 'Rollen en rechten beheren',
     'role_manage_entity_permissions' => 'Beheer alle boeken-, hoofdstukken- en paginaresitrcties',
     'role_manage_own_entity_permissions' => 'Beheer restricties van je eigen boeken, hoofdstukken en pagina\'s',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'Beheer app instellingen',
     'role_asset' => 'Asset Permissies',
     'role_asset_desc' => 'Deze permissies bepalen de standaardtoegangsrechten. Permissies op boeken, hoofdstukken en pagina\'s overschrijven deze instelling.',
+    'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.',
     'role_all' => 'Alles',
     'role_own' => 'Eigen',
     'role_controlled_by_asset' => 'Gecontroleerd door de asset waar deze is geüpload',
@@ -81,19 +98,24 @@ return [
     'role_users' => 'Gebruikers in deze rol',
     'role_users_none' => 'Geen enkele gebruiker heeft deze rol',
 
-    /**
-     * Users
-     */
-
+    // Users
     'users' => 'Gebruikers',
     'user_profile' => 'Gebruikersprofiel',
     'users_add_new' => 'Gebruiker toevoegen',
     'users_search' => 'Gebruiker zoeken',
+    'users_details' => 'User Details',
+    'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.',
+    'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.',
     'users_role' => 'Gebruikersrollen',
+    'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
+    'users_password' => 'User Password',
+    'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 6 characters long.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => 'External Authentication ID',
+    'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your LDAP system.',
     'users_password_warning' => 'Vul onderstaande formulier alleen in als je het wachtwoord wilt aanpassen:',
     'users_system_public' => 'De eigenschappen van deze gebruiker worden voor elke gastbezoeker gebruikt. Er kan niet mee ingelogd worden en wordt automatisch toegewezen.',
-    'users_books_view_type' => 'Voorkeursuitleg voor het weergeven van boeken',
     'users_delete' => 'Verwijder gebruiker',
     'users_delete_named' => 'Verwijder gebruiker :userName',
     'users_delete_warning' => 'Dit zal de gebruiker \':userName\' volledig uit het systeem verwijderen.',
@@ -105,10 +127,40 @@ return [
     'users_avatar' => 'Avatar',
     'users_avatar_desc' => 'De afbeelding moet vierkant zijn en ongeveer 256px breed.',
     'users_preferred_language' => 'Voorkeurstaal',
+    'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
     'users_social_accounts' => 'Social Accounts',
     'users_social_accounts_info' => 'Hier kun je accounts verbinden om makkelijker in te loggen. Via je profiel kun je ook weer rechten intrekken die bij deze social accountsh horen.',
     'users_social_connect' => 'Account Verbinden',
     'users_social_disconnect' => 'Account Ontkoppelen',
     'users_social_connected' => ':socialAccount account is succesvol aan je profiel gekoppeld.',
     'users_social_disconnected' => ':socialAccount account is succesvol ontkoppeld van je profiel.',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index b75af7485ffb7baf86a2faaf46f6ab4107956a3d..05e09d8a19d7b2b61899bee8b4f1d89a007960d9 100644 (file)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => 'The :attribute must be accepted.',
     'active_url'           => 'The :attribute is not a valid URL.',
     'after'                => 'The :attribute must be a date after :date.',
@@ -35,12 +30,41 @@ return [
     'digits'               => 'The :attribute must be :digits digits.',
     'digits_between'       => 'The :attribute must be between :min and :max digits.',
     'email'                => 'The :attribute must be a valid email address.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => 'The :attribute field is required.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => 'The selected :attribute is invalid.',
     'image'                => 'The :attribute must be an image.',
+    'image_extension'      => 'The :attribute must have a valid & supported image extension.',
     'in'                   => 'The selected :attribute is invalid.',
     'integer'              => 'The :attribute must be an integer.',
     'ip'                   => 'The :attribute must be a valid IP address.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => 'The :attribute may not be greater than :max.',
         'file'    => 'The :attribute may not be greater than :max kilobytes.',
@@ -54,7 +78,9 @@ return [
         'string'  => 'The :attribute must be at least :min characters.',
         'array'   => 'The :attribute must have at least :min items.',
     ],
+    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => 'The selected :attribute is invalid.',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => 'The :attribute must be a number.',
     'regex'                => 'The :attribute format is invalid.',
     'required'             => 'The :attribute field is required.',
@@ -74,35 +100,15 @@ return [
     'timezone'             => 'The :attribute must be a valid zone.',
     'unique'               => 'The :attribute has already been taken.',
     'url'                  => 'The :attribute format is invalid.',
+    'uploaded'             => 'The file could not be uploaded. The server may not accept files of this size.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => 'Password confirmation required',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
index 7013be566eeceae9ac0c746bb9b4eda2f1d2adbc..08a35c30def0cd13ede2b095f5eddc07062efa71 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => 'utworzono stronę',
     'page_create_notification'    => 'Strona utworzona pomyślnie',
index 5cec651a9aeafec5ef73231990ae2cb081feac05..1e135880e475c24ce1ac79a80bfb5c3c49500ab8 100644 (file)
@@ -1,21 +1,15 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => 'Wprowadzone poświadczenia są nieprawidłowe.',
     'throttle' => 'Zbyt wiele prób logowania. Spróbuj ponownie za :seconds s.',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => 'Zarejestruj się',
     'log_in' => 'Zaloguj się',
     'log_in_with' => 'Zaloguj się za pomocą :socialDriver',
@@ -27,11 +21,13 @@ return [
     'email' => 'E-mail',
     'password' => 'Hasło',
     'password_confirm' => 'Potwierdzenie hasła',
-    'password_hint' => 'Musi mieć więcej niż 5 znaków',
+    'password_hint' => 'Musi mieć więcej niż 7 znaków',
     'forgot_password' => 'Zapomniałem hasła',
     'remember_me' => 'Zapamiętaj mnie',
     'ldap_email_hint' => 'Wprowadź adres e-mail dla tego konta.',
     'create_account' => 'Utwórz konto',
+    'already_have_account' => 'Already have an account?',
+    'dont_have_account' => 'Don\'t have an account?',
     'social_login' => 'Logowanie za pomocą konta społecznościowego',
     'social_registration' => 'Rejestracja za pomocą konta społecznościowego',
     'social_registration_text' => 'Zarejestruj się za pomocą innej usługi.',
@@ -43,23 +39,18 @@ return [
     'register_success' => 'Dziękujemy za rejestrację! Zostałeś zalogowany automatycznie.',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => 'Resetowanie hasła',
     'reset_password_send_instructions' => 'Wprowadź adres e-mail powiązany z Twoim kontem, by otrzymać link do resetowania hasła.',
     'reset_password_send_button' => 'Wyślij link do resetowania hasła',
     'reset_password_sent_success' => 'Wysłano link do resetowania hasła na adres :email.',
     'reset_password_success' => 'Hasło zostało zresetowane pomyślnie.',
-
     'email_reset_subject' => 'Resetowanie hasła do :appName',
     'email_reset_text' => 'Otrzymujesz tę wiadomość ponieważ ktoś zażądał zresetowania hasła do Twojego konta.',
     'email_reset_not_requested' => 'Jeśli to nie Ty złożyłeś żądanie zresetowania hasła, zignoruj tę wiadomość.',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => 'Potwierdź swój adres e-mail w :appName',
     'email_confirm_greeting' => 'Dziękujemy za dołączenie do :appName!',
     'email_confirm_text' => 'Prosimy byś potwierdził swoje hasło klikając przycisk poniżej:',
@@ -73,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Aby potwierdzić swoje konto kliknij w link wysłany w wiadomości po rejestracji.',
     'email_not_confirmed_resend' => 'Jeśli wiadomość do Ciebie nie dotarła możesz wysłać ją ponownie wypełniając formularz poniżej.',
     'email_not_confirmed_resend_button' => 'Wyślij ponownie wiadomość z potwierdzeniem',
-];
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
+];
\ No newline at end of file
index b979252220bf35d48ba04674c4f59334448c8d36..d5bf0f199e9f876e08c7d13b91a86491bb6333df 100644 (file)
@@ -1,31 +1,30 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => 'Anuluj',
     'confirm' => 'Zatwierdź',
     'back' => 'Wstecz',
     'save' => 'Zapisz',
     'continue' => 'Kontynuuj',
     'select' => 'Wybierz',
+    'toggle_all' => 'Toggle All',
     'more' => 'Więcej',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => 'Nazwa',
     'description' => 'Opis',
     'role' => 'Rola',
     'cover_image' => 'Zdjęcie z okładki',
     'cover_image_description' => 'Ten obraz powinien posiadać wymiary około 440x250px.',
     
-    /**
-     * Actions
-     */
+    // Actions
     'actions' => 'Akcje',
     'view' => 'Widok',
+    'view_all' => 'View All',
     'create' => 'Utwórz',
     'update' => 'Zaktualizuj',
     'edit' => 'Edytuj',
@@ -40,9 +39,16 @@ return [
     'remove' => 'Usuń',
     'add' => 'Dodaj',
 
-    /**
-     * Misc
-     */
+    // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
+    'sort_name' => 'Name',
+    'sort_created_at' => 'Created Date',
+    'sort_updated_at' => 'Updated Date',
+
+    // Misc
     'deleted_user' => 'Użytkownik usunięty',
     'no_activity' => 'Brak aktywności do wyświetlenia',
     'no_items' => 'Brak elementów do wyświetlenia',
@@ -53,16 +59,18 @@ return [
     'grid_view' => 'Widok kafelkowy',
     'list_view' => 'Widok listy',
     'default' => 'Domyślny',
+    'breadcrumb' => 'Breadcrumb',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => 'Zobacz profil',
     'edit_profile' => 'Edytuj profil',
 
-    /**
-     * Email Content
-     */
+    // Layout tabs
+    'tab_info' => 'Info',
+    'tab_content' => 'Content',
+
+    // Email Content
     'email_action_help' => 'Jeśli masz problem z kliknięciem przycisku ":actionText", skopiuj i wklej poniższy adres URL w nowej karcie swojej przeglądarki:',
     'email_rights' => 'Wszelkie prawa zastrzeżone',
 ];
index 8c5429088395027f944e5f7d7029e221d1c4102f..9c044d23773a646b10627bd4e9d2918bed5c5629 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => 'Wybór obrazka',
     'image_all' => 'Wszystkie',
     'image_all_title' => 'Zobacz wszystkie obrazki',
@@ -24,9 +25,7 @@ return [
     'image_delete_success' => 'Obrazek usunięty pomyślnie',
     'image_upload_remove' => 'Usuń',
 
-    /**
-     * Code editor
-     */
+    // Code Editor
     'code_editor' => 'Edytuj kod',
     'code_language' => 'Język kodu',
     'code_content' => 'Zawartość kodu',
index 3dad5e2e38c9a7c8c0e6466eb7b4153090d47879..a87959598da6cca946e11feb7426a2201c4bfad9 100644 (file)
@@ -1,14 +1,17 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => 'Ostatnio utworzone',
     'recently_created_pages' => 'Ostatnio utworzone strony',
     'recently_updated_pages' => 'Ostatnio zaktualizowane strony',
     'recently_created_chapters' => 'Ostatnio utworzone rozdziały',
     'recently_created_books' => 'Ostatnio utworzone podręczniki',
+    'recently_created_shelves' => 'Recently Created Shelves',
     'recently_update' => 'Ostatnio zaktualizowane',
     'recently_viewed' => 'Ostatnio wyświetlane',
     'recent_activity' => 'Ostatnia aktywność',
@@ -19,7 +22,6 @@ return [
     'meta_created_name' => 'Utworzono :timeLength przez :user',
     'meta_updated' => 'Zaktualizowano :timeLength',
     'meta_updated_name' => 'Zaktualizowano :timeLength przez :user',
-    'x_pages' => ':count stron',
     'entity_select' => 'Wybór obiektu',
     'images' => 'Obrazki',
     'my_recent_drafts' => 'Moje ostatnie wersje robocze',
@@ -32,17 +34,13 @@ return [
     'export_pdf' => 'Plik PDF',
     'export_text' => 'Plik tekstowy',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => 'Uprawnienia',
     'permissions_intro' => 'Jeśli włączone są indywidualne uprawnienia, to te uprawnienia będą miały priorytet względem pozostałych ustawionych uprawnień ról.',
     'permissions_enable' => 'Włącz własne uprawnienia',
     'permissions_save' => 'Zapisz uprawnienia',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => 'Wyniki wyszukiwania',
     'search_total_results_found' => ':count znalezionych wyników|:count ogółem znalezionych wyników',
     'search_clear' => 'Wyczyść wyszukiwanie',
@@ -66,17 +64,17 @@ return [
     'search_created_after' => 'Utworzone po',
     'search_set_date' => 'Ustaw datę',
     'search_update' => 'Zaktualizuj wyszukiwanie',
-    
-    /**
-     * Shelves
-     */
+
+    // Shelves
     'shelf' => 'Półka',
     'shelves' => 'Półki',
+    'x_shelves' => ':count Shelf|:count Shelves',
     'shelves_long' => 'Półki',
     'shelves_empty' => 'Brak utworzonych półek',
     'shelves_create' => 'Utwórz półkę',
     'shelves_popular' => 'Popularne półki',
     'shelves_new' => 'Nowe półki',
+    'shelves_new_action' => 'New Shelf',
     'shelves_popular_empty' => 'Najpopularniejsze półki pojawią się w tym miejscu.',
     'shelves_new_empty' => 'Tutaj pojawią się ostatnio utworzone półki.',
     'shelves_save' => 'Zapisz półkę',
@@ -98,10 +96,8 @@ return [
     'shelves_copy_permissions' => 'Skopiuj uprawnienia',
     'shelves_copy_permissions_explain' => 'To spowoduje zastosowanie obecnych ustawień uprawnień dla tej półki do wszystkich podręczników w niej zawartych. Przed aktywacją upewnij się, że wszelkie zmiany w uprawnieniach do tej półki zostały zapisane.',
     'shelves_copy_permission_success' => 'Uprawnienia półki zostały skopiowane do :count podręczników',
-    
-    /**
-     * Books
-     */
+
+    // Books
     'book' => 'Podręcznik',
     'books' => 'Podręczniki',
     'x_books' => ':count Podręcznik|:count Podręczniki',
@@ -109,6 +105,7 @@ return [
     'books_popular' => 'Popularne podręczniki',
     'books_recent' => 'Ostatnie podręczniki',
     'books_new' => 'Nowe podręczniki',
+    'books_new_action' => 'New Book',
     'books_popular_empty' => 'Najpopularniejsze podręczniki pojawią się w tym miejscu.',
     'books_new_empty' => 'Tutaj pojawią się ostatnio utworzone podręczniki.',
     'books_create' => 'Utwórz podręcznik',
@@ -124,7 +121,6 @@ return [
     'books_permissions_updated' => 'Zaktualizowano uprawnienia podręcznika',
     'books_empty_contents' => 'Brak stron lub rozdziałów w tym podręczniku.',
     'books_empty_create_page' => 'Utwórz nową stronę',
-    'books_empty_or' => 'lub',
     'books_empty_sort_current_book' => 'posortuj bieżący podręcznik',
     'books_empty_add_chapter' => 'Dodaj rozdział',
     'books_permissions_active' => 'Uprawnienia podręcznika są aktywne',
@@ -132,12 +128,15 @@ return [
     'books_navigation' => 'Nawigacja po podręczniku',
     'books_sort' => 'Sortuj zawartość podręcznika',
     'books_sort_named' => 'Sortuj podręcznik :bookName',
+    'books_sort_name' => 'Sort by Name',
+    'books_sort_created' => 'Sort by Created Date',
+    'books_sort_updated' => 'Sort by Updated Date',
+    'books_sort_chapters_first' => 'Chapters First',
+    'books_sort_chapters_last' => 'Chapters Last',
     'books_sort_show_other' => 'Pokaż inne podręczniki',
     'books_sort_save' => 'Zapisz nową kolejność',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => 'Rozdział',
     'chapters' => 'Rozdziały',
     'x_chapters' => ':count Rozdział|:count Rozdziały',
@@ -161,11 +160,10 @@ return [
     'chapters_permissions_success' => 'Zaktualizowano uprawnienia rozdziału',
     'chapters_search_this' => 'Przeszukaj ten rozdział',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => 'Strona',
     'pages' => 'Strony',
+    'x_pages' => ':count stron',
     'pages_popular' => 'Popularne strony',
     'pages_new' => 'Nowa strona',
     'pages_attachments' => 'Załączniki',
@@ -179,7 +177,7 @@ return [
     'pages_delete_confirm' => 'Czy na pewno chcesz usunąć tę stronę?',
     'pages_delete_draft_confirm' => 'Czy na pewno chcesz usunąć wersje roboczą strony?',
     'pages_editing_named' => 'Edytowanie strony :pageName',
-    'pages_edit_toggle_header' => 'Włącz/wyłącz nagłówek',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'Zapisano wersje roboczą o ',
     'pages_edit_draft' => 'Edytuj wersje roboczą',
     'pages_editing_draft' => 'Edytowanie wersji roboczej',
@@ -213,6 +211,8 @@ return [
     'pages_revisions_created_by' => 'Utworzona przez',
     'pages_revisions_date' => 'Data wersji',
     'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
     'pages_revisions_changelog' => 'Dziennik zmian',
     'pages_revisions_changes' => 'Zmiany',
     'pages_revisions_current' => 'Obecna wersja',
@@ -235,19 +235,20 @@ return [
     ],
     'pages_draft_discarded' => 'Wersja robocza odrzucona, edytor został uzupełniony najnowszą wersją strony',
     'pages_specific' => 'Określona strona',
+    'pages_is_template' => 'Page Template',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => 'Tagi strony',
     'chapter_tags' => 'Tagi rozdziału',
     'book_tags' => 'Tagi podręcznika',
     'shelf_tags' => 'Tagi półki',
     'tag' => 'Tag',
     'tags' =>  'Tagi',
+    'tag_name' =>  'Tag Name',
     'tag_value' => 'Wartość tagu (opcjonalnie)',
     'tags_explain' => "Dodaj tagi by skategoryzować zawartość. \n W celu dokładniejszej organizacji zawartości możesz dodać wartości do tagów.",
     'tags_add' => 'Dodaj kolejny tag',
+    'tags_remove' => 'Remove this tag',
     'attachments' => 'Załączniki',
     'attachments_explain' => 'Prześlij kilka plików lub załącz linki. Będą one widoczne na pasku bocznym strony.',
     'attachments_explain_instant_save' => 'Zmiany są zapisywane natychmiastowo.',
@@ -273,19 +274,22 @@ return [
     'attachments_file_uploaded' => 'Plik załączony pomyślnie',
     'attachments_file_updated' => 'Plik zaktualizowany pomyślnie',
     'attachments_link_attached' => 'Link pomyślnie dodany do strony',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => 'Użytkownik od :time',
     'profile_created_content' => 'Utworzona zawartość',
     'profile_not_created_pages' => ':userName nie utworzył żadnych stron',
     'profile_not_created_chapters' => ':userName nie utworzył żadnych rozdziałów',
     'profile_not_created_books' => ':userName nie utworzył żadnych podręczników',
+    'profile_not_created_shelves' => ':userName has not created any shelves',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => 'Komentarz',
     'comments' => 'Komentarze',
     'comment_add' => 'Dodaj komentarz',
@@ -303,10 +307,9 @@ return [
     'comment_delete_confirm' => 'Czy na pewno chcesz usunąc ten komentarz?',
     'comment_in_reply_to' => 'W odpowiedzi na :commentId',
 
-     /**
-     * Revision
-     */
+    // Revision
     'revision_delete_confirm' => 'Czy na pewno chcesz usunąć tę wersję?',
+    'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
     'revision_delete_success' => 'Usunięto wersję',
     'revision_cannot_delete_latest' => 'Nie można usunąć najnowszej wersji.'
-];
+];
\ No newline at end of file
index 6377e12620807f732ae9887d06488b6e9b672be2..5a637524303add536e27be7fba5348acec6efc6a 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => 'Nie masz uprawnień do wyświetlenia tej strony.',
     'permissionJson' => 'Nie masz uprawnień do wykonania tej akcji.',
@@ -29,6 +27,7 @@ return [
     'social_account_register_instructions' => 'Jeśli nie masz jeszcze konta, możesz zarejestrować je używając opcji :socialAccount.',
     'social_driver_not_found' => 'Funkcja społecznościowa nie została odnaleziona',
     'social_driver_not_configured' => 'Ustawienia konta :socialAccount nie są poprawne.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => 'Zapis do ścieżki :filePath jest niemożliwy. Upewnij się że aplikacja ma prawa do zapisu plików na serwerze.',
@@ -66,14 +65,15 @@ return [
     'role_cannot_be_edited' => 'Ta rola nie może być edytowana',
     'role_system_cannot_be_deleted' => 'Ta rola jest rolą systemową i nie może zostać usunięta',
     'role_registration_default_cannot_delete' => 'Ta rola nie może zostać usunięta, dopóki jest ustawiona jako domyślna rola użytkownika',
-    
+    'role_cannot_remove_only_admin' => 'This user is the only user assigned to the administrator role. Assign the administrator role to another user before attempting to remove it here.',
+
     // Comments
     'comment_list' => 'Wystąpił błąd podczas pobierania komentarzy.',
     'cannot_add_comment_to_draft' => 'Nie możesz dodawać komentarzy do wersji roboczej.',
     'comment_add' => 'Wystąpił błąd podczas dodwania / aktualizaowania komentarza.',
     'comment_delete' => 'Wystąpił błąd podczas usuwania komentarza.',
     'empty_comment' => 'Nie można dodać pustego komentarza.',
-    
+
     // Error pages
     '404_page_not_found' => 'Strona nie została znaleziona',
     'sorry_page_not_found' => 'Przepraszamy, ale strona której szukasz nie została znaleziona.',
@@ -81,4 +81,5 @@ return [
     'error_occurred' => 'Wystąpił błąd',
     'app_down' => ':appName jest aktualnie wyłączona',
     'back_soon' => 'Niedługo zostanie uruchomiona ponownie.',
+
 ];
index 564694190f965bb972ca8829266fd9819a6f1148..c3d80d68159910dc25f3d669c6ee80dd0127a668 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; Poprzednia',
     'next'     => 'Następna &raquo;',
 
index a9103d593afbe46207c30c9780bdbfe6aa22098f..8c9eae227c7656d8e2bd9f645a56ab0db83de87f 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => 'Hasło musi zawierać co najmniej 6 znaków i być zgodne z powtórzeniem.',
     'user' => "Nie znaleziono użytkownika o takim adresie e-mail.",
     'token' => 'Ten token resetowania hasła jest nieprawidłowy.',
index e1c8c7b8d237f4954554d71a1c16b54c24388b7c..2c17657e4f057a4c88c878d0084ddca205e680bf 100644 (file)
@@ -1,32 +1,35 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
-
+    // Common Messages
     'settings' => 'Ustawienia',
     'settings_save' => 'Zapisz ustawienia',
     'settings_save_success' => 'Ustawienia zapisane',
 
-    /**
-     * App settings
-     */
-
-    'app_settings' => 'Ustawienia aplikacji',
+    // App Settings
+    'app_customization' => 'Customization',
+    'app_features_security' => 'Features & Security',
     'app_name' => 'Nazwa aplikacji',
     'app_name_desc' => 'Ta nazwa jest wyświetlana w nagłówku i e-mailach.',
     'app_name_header' => 'Pokazać nazwę aplikacji w nagłówku?',
+    'app_public_access' => 'Public Access',
+    'app_public_access_desc' => 'Enabling this option will allow visitors, that are not logged-in, to access content in your BookStack instance.',
+    'app_public_access_desc_guest' => 'Access for public visitors can be controlled through the "Guest" user.',
+    'app_public_access_toggle' => 'Allow public access',
     'app_public_viewing' => 'Zezwolić na publiczne przeglądanie?',
     'app_secure_images' => 'Włączyć przesyłanie obrazów o wyższym poziomie bezpieczeństwa?',
+    'app_secure_images_toggle' => 'Enable higher security image uploads',
     'app_secure_images_desc' => 'Ze względów wydajnościowych wszystkie obrazki są publiczne. Ta opcja dodaje dodatkowy, trudny do odgadnięcia losowy ciąg na początku nazwy obrazka. Upewnij się że indeksowanie katalogów jest zablokowane, aby uniemożliwić łatwy dostęp do obrazków.',
     'app_editor' => 'Edytor strony',
     'app_editor_desc' => 'Wybierz edytor używany przez użytkowników do edycji zawartości.',
     'app_custom_html' => 'Własna zawartość w tagu <head>',
     'app_custom_html_desc' => 'Zawartość dodana tutaj zostanie dołączona na dole sekcji <head> każdej strony. Przydatne przy nadpisywaniu styli lub dodawaniu analityki.',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'Logo aplikacji',
     'app_logo_desc' => 'Ten obrazek powinien mieć nie więcej niż 43px wysokosci. <br>Większe obrazki zostaną zmniejszone.',
     'app_primary_color' => 'Podstawowy kolor aplikacji',
@@ -35,25 +38,23 @@ return [
     'app_homepage_desc' => 'Wybierz widok, który będzie wyświetlany na stronie głównej zamiast w widoku domyślnego. Uprawnienia dostępowe są ignorowane dla wybranych stron.',
     'app_homepage_select' => 'Wybierz stronę',
     'app_disable_comments' => 'Wyłącz komentarze',
+    'app_disable_comments_toggle' => 'Disable comments',
     'app_disable_comments_desc' => 'Wyłącz komentarze na wszystkich stronach w aplikacji. Istniejące komentarze nie będą pokazywane.',
 
-    /**
-     * Registration settings
-     */
-
+    // Registration Settings
     'reg_settings' => 'Ustawienia rejestracji',
-    'reg_allow' => 'Zezwolić na rejestrację?',
+    'reg_enable' => 'Enable Registration',
+    'reg_enable_toggle' => 'Enable registration',
+    'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
     'reg_default_role' => 'Domyślna rola użytkownika po rejestracji',
-    'reg_confirm_email' => 'Wymagać potwierdzenia adresu e-mail?',
+    'reg_email_confirmation' => 'Email Confirmation',
+    'reg_email_confirmation_toggle' => 'Require email confirmation',
     'reg_confirm_email_desc' => 'Jeśli restrykcje domenowe zostały ustawione, potwierdzenie adresu stanie się konieczne, a poniższa wartośc zostanie zignorowana.',
     'reg_confirm_restrict_domain' => 'Restrykcje domenowe dot. adresu e-mail',
     'reg_confirm_restrict_domain_desc' => 'Wprowadź listę domen adresów e-mail, rozdzieloną przecinkami, którym chciałbyś zezwolić na rejestrację. Wymusi to konieczność potwierdzenia adresu e-mail przez użytkownika przed uzyskaniem dostępu do aplikacji. <br> Pamiętaj, że użytkownicy będą mogli zmienić adres e-mail po rejestracji.',
     'reg_confirm_restrict_domain_placeholder' => 'Brak restrykcji',
 
-    /**
-     * Maintenance settings
-     */
-
+    // Maintenance settings
     'maint' => 'Konserwacja',
     'maint_image_cleanup' => 'Czyszczenie obrazków',
     'maint_image_cleanup_desc' => "Skanuje zawartość strony i poprzednie wersje, aby sprawdzić, które obrazy i rysunki są aktualnie używane, a które obrazy są zbędne. Przed uruchomieniem tej opcji należy utworzyć pełną kopię zapasową bazy danych i obrazków.",
@@ -63,10 +64,7 @@ return [
     'maint_image_cleanup_success' => ':count potencjalnie nieużywane obrazki zostały znalezione i usunięte!',
     'maint_image_cleanup_nothing_found' => 'Nie znaleziono żadnych nieużywanych obrazków. Nic nie zostało usunięte!',
 
-    /**
-     * Role settings
-     */
-
+    // Role Settings
     'roles' => 'Role',
     'role_user_roles' => 'Role użytkowników',
     'role_create' => 'Utwórz nową rolę',
@@ -87,6 +85,7 @@ return [
     'role_manage_roles' => 'Zarządzanie rolami i uprawnieniami ról',
     'role_manage_entity_permissions' => 'Zarządzanie uprawnieniami podręczników, rozdziałów i stron',
     'role_manage_own_entity_permissions' => 'Zarządzanie uprawnieniami własnych podręczników, rozdziałów i stron',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'Zarządzanie ustawieniami aplikacji',
     'role_asset' => 'Zarządzanie zasobami',
     'role_asset_desc' => 'Te ustawienia kontrolują zarządzanie zasobami systemu. Uprawnienia podręczników, rozdziałów i stron nadpisują te ustawienia.',
@@ -99,16 +98,22 @@ return [
     'role_users' => 'Użytkownicy w tej roli',
     'role_users_none' => 'Brak użytkowników zapisanych do tej roli',
 
-    /**
-     * Users
-     */
-
+    // Users
     'users' => 'Użytkownicy',
     'user_profile' => 'Profil użytkownika',
     'users_add_new' => 'Dodaj użytkownika',
     'users_search' => 'Wyszukaj użytkownika',
+    'users_details' => 'User Details',
+    'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.',
+    'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.',
     'users_role' => 'Role użytkownika',
+    'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
+    'users_password' => 'User Password',
+    'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 6 characters long.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => 'Zewnętrzne identyfikatory autentykacji',
+    'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your LDAP system.',
     'users_password_warning' => 'Wypełnij poniżej tylko jeśli chcesz zmienić swoje hasło:',
     'users_system_public' => 'Ten użytkownik reprezentuje każdego gościa odwiedzającego tę aplikację. Nie można się na niego zalogować, lecz jest przyznawany automatycznie.',
     'users_delete' => 'Usuń użytkownika',
@@ -122,10 +127,40 @@ return [
     'users_avatar' => 'Avatar użytkownika',
     'users_avatar_desc' => 'Ten obrazek powinien posiadać wymiary 256x256px.',
     'users_preferred_language' => 'Preferowany język',
+    'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
     'users_social_accounts' => 'Konta społecznościowe',
     'users_social_accounts_info' => 'Tutaj możesz połączyć kilka kont społecznościowych w celu łatwiejszego i szybszego logowania. Odłączenie konta tutaj nie autoryzowało dostępu. Odwołaj dostęp z ustawień profilu na podłączonym koncie społecznościowym.',
     'users_social_connect' => 'Podłącz konto',
     'users_social_disconnect' => 'Odłącz konto',
     'users_social_connected' => ':socialAccount zostało dodane do Twojego profilu.',
     'users_social_disconnected' => ':socialAccount zostało odłączone od Twojego profilu.',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index 6a7c13e806acc7ae277cd82fa268d4b3daa928bf..d5e5ab343d5b399778093de9957b70b51e95db1a 100644 (file)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => ':attribute musi zostać zaakceptowany.',
     'active_url'           => ':attribute nie jest prawidłowym adresem URL.',
     'after'                => ':attribute musi być datą następującą po :date.',
@@ -35,12 +30,41 @@ return [
     'digits'               => ':attribute musi mieć :digits cyfr.',
     'digits_between'       => ':attribute musi mieć od :min do :max cyfr.',
     'email'                => ':attribute musi być prawidłowym adresem e-mail.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => ':attribute jest wymagany.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => 'Wybrana wartość :attribute jest nieprawidłowa.',
     'image'                => ':attribute musi być obrazkiem.',
+    'image_extension'      => 'The :attribute must have a valid & supported image extension.',
     'in'                   => 'Wybrana wartość :attribute jest nieprawidłowa.',
     'integer'              => ':attribute musi być liczbą całkowitą.',
     'ip'                   => ':attribute musi być prawidłowym adresem IP.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => 'Wartość :attribute nie może być większa niż :max.',
         'file'    => 'Wielkość :attribute nie może być większa niż :max kilobajtów.',
@@ -54,7 +78,9 @@ return [
         'string'  => 'Długość :attribute nie może być mniejsza niż :min znaków.',
         'array'   => 'Rozmiar :attribute musi posiadać co najmniej :min elementy.',
     ],
+    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => 'Wartość :attribute jest nieprawidłowa.',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attribute musi być liczbą.',
     'regex'                => 'Format :attribute jest nieprawidłowy.',
     'required'             => 'Pole :attribute jest wymagane.',
@@ -74,35 +100,15 @@ return [
     'timezone'             => ':attribute musi być prawidłową strefą czasową.',
     'unique'               => ':attribute zostało już zajęte.',
     'url'                  => 'Format :attribute jest nieprawidłowy.',
+    'uploaded'             => 'The file could not be uploaded. The server may not accept files of this size.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => 'Potwierdzenie hasła jest wymagane.',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
index 91417cc8b97f08e31bcb466552c674a811533a0d..5a40886004bf9593f08a10e59c5711f89be5a765 100644 (file)
@@ -4,7 +4,7 @@
  * Is used for all the text within activity logs & notifications.
  */
 return [
-    
+
     // Pages
     'page_create'                 => 'página criada',
     'page_create_notification'    => 'Página criada com sucesso',
@@ -41,7 +41,7 @@ return [
     'bookshelf_update'                 => 'prateleira de livros atualizada',
     'bookshelf_update_notification'    => 'Prateleira de Livros atualizada com sucesso',
     'bookshelf_delete'                 => 'prateleira de livros excluída',
-    'bookshelf_delete_notification'    => 'Prateleira de Livros excluída com sucesso',    
+    'bookshelf_delete_notification'    => 'Prateleira de Livros excluída com sucesso',
 
     // Other
     'commented_on'                => 'comentou em',
index 20dc690af868ba79c3defc33ed1b2e006ed522c1..21c16699c07b6fa78a8a808c8f1bee63a8597812 100644 (file)
@@ -21,7 +21,7 @@ return [
     'email' => 'E-mail',
     'password' => 'Senha',
     'password_confirm' => 'Confirmar Senha',
-    'password_hint' => 'Senha deverá ser maior que 5 caracteres',
+    'password_hint' => 'Senha deverá ser maior que 7 caracteres',
     'forgot_password' => 'Esqueceu a senha?',
     'remember_me' => 'Lembrar de mim',
     'ldap_email_hint' => 'Por favor, digite um e-mail para essa conta.',
@@ -64,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Por favor, clique no link no e-mail que foi enviado após o registro.',
     'email_not_confirmed_resend' => 'Caso não encontre o e-mail você poderá reenviar a confirmação usando o formulário abaixo.',
     'email_not_confirmed_resend_button' => 'Reenviar o e-mail de confirmação',
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
 ];
\ No newline at end of file
index c6750a9540e47770dfdf3f926fd696b4496c1e60..03d9b1d8e4b216153b20ac3ec1237252309158cb 100644 (file)
@@ -40,6 +40,10 @@ return [
     'add' => 'Adicionar',
 
     // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
     'sort_name' => 'Nome',
     'sort_created_at' => 'Data de Criação',
     'sort_updated_at' => 'Data de Atualização',
@@ -55,8 +59,10 @@ return [
     'grid_view' => 'Visualização em Grade',
     'list_view' => 'Visualização em Lista',
     'default' => 'Padrão',
+    'breadcrumb' => 'Breadcrumb',
 
     // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => 'Visualizar Perfil',
     'edit_profile' => 'Editar Perfil',
 
@@ -67,4 +73,4 @@ return [
     // Email Content
     'email_action_help' => 'Se você estiver tendo problemas ao clicar o botão ":actionText", copie e cole a URL abaixo no seu navegador:',
     'email_rights' => 'Todos os direitos reservados',
-];
\ No newline at end of file
+];
index b9f1c3a38b456219e4be5f166b0e72dbe31e111c..e983e9f8df194aa03cccf10b63813d205e25d934 100644 (file)
@@ -25,7 +25,7 @@ return [
     'image_delete_success' => 'Imagem excluída com sucesso',
     'image_upload_remove' => 'Remover',
 
-    // Code editor
+    // Code Editor
     'code_editor' => 'Editar Código',
     'code_language' => 'Linguagem do Código',
     'code_content' => 'Código',
index 7ce5ef01ef89fc24d977bf6dc67a75cf9244ae98..c0e2c5ee9fd6e438af0052d1a3e1c6ce708454fc 100644 (file)
@@ -176,6 +176,7 @@ return [
     'pages_delete_confirm' => 'Tem certeza que deseja excluir a página?',
     'pages_delete_draft_confirm' => 'Tem certeza que deseja excluir o rascunho de página?',
     'pages_editing_named' => 'Editando a Página :pageName',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'Salvar Rascunho',
     'pages_edit_draft' => 'Editar rascunho de Página',
     'pages_editing_draft' => 'Editando Rascunho',
@@ -210,8 +211,8 @@ return [
     'pages_revisions_date' => 'Data da Revisão',
     'pages_revisions_number' => '#',
     'pages_revisions_numbered' => 'Revisão #:id',
-    'pages_revisions_changelog' => 'Changelog',
     'pages_revisions_numbered_changes' => 'Alterações da Revisão #:id',
+    'pages_revisions_changelog' => 'Changelog',
     'pages_revisions_changes' => 'Mudanças',
     'pages_revisions_current' => 'Versão atual',
     'pages_revisions_preview' => 'Preview',
@@ -233,17 +234,20 @@ return [
     ],
     'pages_draft_discarded' => 'Rascunho descartado. O editor foi atualizado com a página atualizada',
     'pages_specific' => 'Página Específica',
+    'pages_is_template' => 'Page Template',
 
-    // Editor sidebar
+    // Editor Sidebar
     'page_tags' => 'Tags de Página',
     'chapter_tags' => 'Tags de Capítulo',
     'book_tags' => 'Tags de Livro',
     'shelf_tags' => 'Tags de Prateleira',
     'tag' => 'Tag',
-    'tags' =>  '',
+    'tags' =>  'Tags',
+    'tag_name' =>  'Tag Name',
     'tag_value' => 'Valor da Tag (Opcional)',
     'tags_explain' => "Adicione algumas tags para melhor categorizar seu conteúdo. \n Você pode atrelar um valor para uma tag para uma organização mais consistente.",
     'tags_add' => 'Adicionar outra tag',
+    'tags_remove' => 'Remove this tag',
     'attachments' => 'Anexos',
     'attachments_explain' => 'Faça o Upload de alguns arquivos ou anexo algum link para ser mostrado na sua página. Eles estarão visíveis na barra lateral à direita da página.',
     'attachments_explain_instant_save' => 'Mudanças são salvas instantaneamente.',
@@ -269,6 +273,12 @@ return [
     'attachments_file_uploaded' => 'Upload de arquivo efetuado com sucesso',
     'attachments_file_updated' => 'Arquivo atualizado com sucesso',
     'attachments_link_attached' => 'Link anexado com sucesso à página',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
     // Profile View
     'profile_user_for_x' => 'Usuário por :time',
index c5b1a9f9213a24c21e73e0d53d86d59dd4aedc06..150cb59112ba1503c56591d407ca2978ed13582c 100644 (file)
@@ -27,13 +27,14 @@ return [
     'social_account_register_instructions' => 'Se você não tem uma conta, você poderá fazer o registro usando a opção :socialAccount',
     'social_driver_not_found' => 'Social driver não encontrado',
     'social_driver_not_configured' => 'Seus parâmetros socials de :socialAccount não estão configurados corretamente.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => 'O caminho de destino (:filePath) de upload de arquivo não possui permissão de escrita. Certifique-se que ele possui direitos de escrita no servidor.',
     'cannot_get_image_from_url' => 'Não foi possivel capturar a imagem a partir de :url',
     'cannot_create_thumbs' => 'O servidor não pôde criar as miniaturas de imagem. Por favor, verifique se a extensão GD PHP está instalada.',
     'server_upload_limit' => 'O servidor não permite o upload de arquivos com esse tamanho. Por favor, tente fazer o upload de arquivos de menor tamanho.',
-    'uploaded' => 'O servidor não permite o upload de arquivos com esse tamanho. Por favor, tente fazer o upload de arquivos de menor tamanho.',
+    'uploaded'  => 'O servidor não permite o upload de arquivos com esse tamanho. Por favor, tente fazer o upload de arquivos de menor tamanho.',
     'image_upload_error' => 'Um erro aconteceu enquanto o servidor tentava efetuar o upload da imagem',
     'image_upload_type_error' => 'O tipo de imagem que está sendo feito upload é inválido',
     'file_upload_timeout' => 'O upload do arquivo expirou.',
@@ -66,7 +67,7 @@ return [
     'role_registration_default_cannot_delete' => 'Esse perfil não poderá se excluído enquando estiver registrado como o perfil padrão',
     'role_cannot_remove_only_admin' => 'Este usuário é o único usuário atribuído ao perfil de administrador. Atribua o perfil de administrador a outro usuário antes de tentar removê-lo aqui.',
 
-    // comments
+    // Comments
     'comment_list' => 'Ocorreu um erro ao buscar os comentários.',
     'cannot_add_comment_to_draft' => 'Você não pode adicionar comentários a um rascunho.',
     'comment_add' => 'Ocorreu um erro ao adicionar o comentário.',
@@ -80,5 +81,5 @@ return [
     'error_occurred' => 'Um erro ocorreu',
     'app_down' => ':appName está fora do ar no momento',
     'back_soon' => 'Voltaremos em seguida.',
-    
+
 ];
index 4bb8f37e0752023f2824e07509514b01ec1c7c8e..03283d752e8d4d1b8a020a65e0ab3b8c2aeb9a32 100644 (file)
@@ -41,7 +41,7 @@ return [
     'app_disable_comments_toggle' => 'Desativar comentários',
     'app_disable_comments_desc' => 'Desativar comentários em todas as páginas no aplicativo. Os comentários existentes não são exibidos.',
 
-    // Registration settings
+    // Registration Settings
     'reg_settings' => 'Registro',
     'reg_enable' => 'Habilitar Registro',
     'reg_enable_toggle' => 'Habilitar registro',
@@ -64,7 +64,7 @@ return [
     'maint_image_cleanup_success' => ':count imagens potencialmente não utilizadas foram encontradas e excluídas!',
     'maint_image_cleanup_nothing_found' => 'Nenhuma imagem não utilizada foi encontrada, nada foi excluído!',
 
-    // Role settings
+    // Role Settings
     'roles' => 'Perfis',
     'role_user_roles' => 'Perfis de Usuário',
     'role_create' => 'Criar novo Perfil',
@@ -85,6 +85,7 @@ return [
     'role_manage_roles' => 'Gerenciar Perfis & Permissões de Perfis',
     'role_manage_entity_permissions' => 'Gerenciar todos os livros, capítulos e permissões de páginas',
     'role_manage_own_entity_permissions' => 'Gerenciar permissões de seu próprio livro, capítulo e paginas',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'Gerenciar configurações de app',
     'role_asset' => 'Permissões de Ativos',
     'role_asset_desc' => 'Essas permissões controlam o acesso padrão para os ativos dentro do sistema. Permissões em Livros, Capítulos e Páginas serão sobrescritas por essas permissões.',
@@ -109,6 +110,8 @@ return [
     'users_role_desc' => 'Selecione os perfis para os quais este usuário será atribuído. Se um usuário for atribuído a multiplos perfis, as permissões destes perfis serão empilhadas e eles receberão todas as habilidades dos perfis atribuídos.',
     'users_password' => 'Senha do Usuário',
     'users_password_desc' => 'Defina uma senha usada para fazer login na aplicação. Esta deve ter pelo menos 5 caracteres.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => 'ID de Autenticação Externa',
     'users_external_auth_id_desc' => 'Este é o ID usado para corresponder a este usuário ao se comunicar com seu sistema LDAP.',
     'users_password_warning' => 'Preencha os dados abaixo caso queira modificar a sua senha:',
@@ -132,34 +135,32 @@ return [
     'users_social_connected' => 'Conta :socialAccount foi conectada com sucesso ao seu perfil.',
     'users_social_disconnected' => 'Conta :socialAccount foi desconectada com sucesso de seu perfil.',
 
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
index 3d4b51f036560a917b535d6d4b3569296a1eb7e5..c087fd0ab129c66f51b17bcab94501b80eb2a77b 100644 (file)
@@ -30,13 +30,41 @@ return [
     'digits'               => 'O campo :attribute deve ter :digits dígitos.',
     'digits_between'       => 'O campo :attribute deve ter entre :min e :max dígitos.',
     'email'                => 'O campo :attribute deve ser um e-mail válido.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => 'O campo :attribute é requerido.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => 'O atributo :attribute selecionado não é válido.',
     'image'                => 'O campo :attribute deve ser uma imagem.',
     'image_extension'      => 'O campo :attribute deve ter uma extensão de imagem válida & suportada.',
     'in'                   => 'The selected :attribute is invalid.',
     'integer'              => 'O campo :attribute deve ser um número inteiro.',
     'ip'                   => 'O campo :attribute deve ser um IP válido.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => 'O valor para o campo :attribute não deve ser maior que :max.',
         'file'    => 'O valor para o campo :attribute não deve ter tamanho maior que :max kilobytes.',
@@ -52,6 +80,7 @@ return [
     ],
     'no_double_extension'  => 'O campo :attribute deve ter apenas uma extensão de arquivo.',
     'not_in'               => 'O campo selecionado :attribute é inválido.',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => 'O campo :attribute deve ser um número.',
     'regex'                => 'O formato do campo :attribute é inválido.',
     'required'             => 'O campo :attribute é requerido.',
index 35eefadc6e575511bfd501019a2565f55bb28827..8be36b8bd2459f6d6faee1234de3c7e2c0495bd4 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => 'создал страницу',
     'page_create_notification'    => 'Страница успешно создана',
@@ -37,6 +35,14 @@ return [
     'book_sort'                   => 'отсортировал книгу',
     'book_sort_notification'      => 'Книга успешно отсортирована',
 
+    // Bookshelves
+    'bookshelf_create'            => 'created Bookshelf',
+    'bookshelf_create_notification'    => 'Bookshelf Successfully Created',
+    'bookshelf_update'                 => 'updated bookshelf',
+    'bookshelf_update_notification'    => 'Bookshelf Successfully Updated',
+    'bookshelf_delete'                 => 'deleted bookshelf',
+    'bookshelf_delete_notification'    => 'Bookshelf Successfully Deleted',
+
     // Other
     'commented_on'                => 'прокомментировал',
 ];
index 3a1fbbf9793aa43a4cf33531ca87cbdbc89a4424..e493ec1972aa193b9fa3375532dcc73e195c39c7 100644 (file)
@@ -1,21 +1,15 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => 'Учетная запись не найдена.',
     'throttle' => 'Слишком много попыток входа. Пожалуйста, попробуйте позже через :seconds секунд.',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => 'Регистрация',
     'log_in' => 'Вход',
     'log_in_with' => 'Вход с :socialDriver',
@@ -27,11 +21,13 @@ return [
     'email' => 'Email',
     'password' => 'Пароль',
     'password_confirm' => 'Подтверждение пароля',
-    'password_hint' => 'Должен быть больше 5 символов',
+    'password_hint' => 'Должен быть больше 7 символов',
     'forgot_password' => 'Забыли пароль?',
     'remember_me' => 'Запомнить меня',
     'ldap_email_hint' => 'Введите email адрес для данной учетной записи.',
     'create_account' => 'Создать аккаунт',
+    'already_have_account' => 'Уже есть аккаунт?',
+    'dont_have_account' => 'У вас нет аккаунта?',
     'social_login' => 'Вход через Соцсеть',
     'social_registration' => 'Регистрация через Соцсеть',
     'social_registration_text' => 'Регистрация и вход через другой сервис.',
@@ -43,23 +39,18 @@ return [
     'register_success' => 'Спасибо за регистрацию! Регистрация и вход в систему выполнены.',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => 'Сброс пароля',
-    'reset_password_send_instructions' => 'Введите свой адрес электронной почты ниже, и вам будет отправлено письмо со ссылкой для сброса пароля.',
+    'reset_password_send_instructions' => 'Введите свой email ниже, и вам будет отправлено письмо со ссылкой для сброса пароля.',
     'reset_password_send_button' => 'Отправить ссылку для сброса',
     'reset_password_sent_success' => 'Ссылка для сброса была отправлена на :email.',
     'reset_password_success' => 'Ваш пароль был успешно сброшен.',
-
     'email_reset_subject' => 'Сбросить ваш :appName пароль',
-    'email_reset_text' => 'Ð\92Ñ\8b Ð¿Ð¾Ð»Ñ\83Ñ\87или Ñ\8dÑ\82о Ð¿Ð¸Ñ\81Ñ\8cмо, Ð¿Ð¾Ñ\82омÑ\83 Ñ\87Ñ\82о Ð²Ñ\8b Ð·Ð°Ð¿Ñ\80оÑ\81или Ñ\81бÑ\80оÑ\81 Ð¿Ð°Ñ\80олÑ\8f Ð´Ð»Ñ\8f Ð²Ð°Ñ\88ей Ñ\83Ñ\87еÑ\82ной Ð·Ð°Ð¿Ð¸Ñ\81и.',
+    'email_reset_text' => 'Вы получили это письмо, потому что запросили сброс пароля для вашей учетной записи.',
     'email_reset_not_requested' => 'Если вы не запрашивали сброса пароля, то никаких дополнительных действий не требуется.',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => 'Подтвердите ваш почтовый адрес на :appName',
     'email_confirm_greeting' => 'Благодарим за участие :appName!',
     'email_confirm_text' => 'Пожалуйста, подтвердите ваш email адрес кликнув на кнопку ниже:',
@@ -73,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Пожалуйста, нажмите на ссылку в письме, которое было отправлено при регистрации.',
     'email_not_confirmed_resend' => 'Если вы не можете найти электронное письмо, вы можете снова отправить письмо с подтверждением по форме ниже.',
     'email_not_confirmed_resend_button' => 'Переотправить письмо с подтверждением',
-];
+
+    // User Invite
+    'user_invite_email_subject' => 'Вас приглашают присоединиться к :appName!',
+    'user_invite_email_greeting' => 'Для вас создан аккаунт в :appName.',
+    'user_invite_email_text' => 'Нажмите кнопку ниже, чтобы задать пароль и получить доступ:',
+    'user_invite_email_action' => 'Установить пароль аккаунту.',
+    'user_invite_page_welcome' => 'Добро пожаловать в :appName!',
+    'user_invite_page_text' => 'Завершите настройку аккаунта, установите пароль для дальнейшего входа в :appName.',
+    'user_invite_page_confirm_button' => 'Подтвердите пароль',
+    'user_invite_success' => 'Пароль установлен, теперь у вас есть доступ к :appName!'
+];
\ No newline at end of file
index b4cef7764954d5d701bfd73ded364936e55781b5..9ead2736ae6f2995e6f452427315da5f95e24e9c 100644 (file)
@@ -1,29 +1,27 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => 'Отмена',
     'confirm' => 'Применить',
     'back' => 'Назад',
     'save' => 'Сохранить',
     'continue' => 'Продолжить',
     'select' => 'Выбрать',
+    'toggle_all' => 'Переключить все',
     'more' => 'Еще',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => 'Имя',
     'description' => 'Описание',
     'role' => 'Роль',
     'cover_image' => 'Обложка',
     'cover_image_description' => 'Изображение должно быть размером около 440x250px.',
-
-    /**
-     * Actions
-     */
+    
+    // Actions
     'actions' => 'Действия',
     'view' => 'Просмотр',
     'view_all' => 'Показать все',
@@ -40,15 +38,17 @@ return [
     'reset' => 'Сбросить',
     'remove' => 'Удалить',
     'add' => 'Добавить',
-    
+
     // Sort Options
+    'sort_options' => 'Параметры сортировки',
+    'sort_direction_toggle' => 'Переключить направления сортировки',
+    'sort_ascending' => 'По возрастанию',
+    'sort_descending' => 'По убыванию',
     'sort_name' => 'По имени',
     'sort_created_at' => 'По дате создания',
     'sort_updated_at' => 'По дате обновления',
 
-    /**
-     * Misc
-     */
+    // Misc
     'deleted_user' => 'Удаленный пользователь',
     'no_activity' => 'Нет действий для просмотра',
     'no_items' => 'Нет доступных элементов',
@@ -59,20 +59,18 @@ return [
     'grid_view' => 'Вид сеткой',
     'list_view' => 'Вид списком',
     'default' => 'По умолчанию',
+    'breadcrumb' => 'Навигация',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Меню профиля',
     'view_profile' => 'Просмотреть профиль',
     'edit_profile' => 'Редактировать профиль',
-    
+
     // Layout tabs
     'tab_info' => 'Информация',
     'tab_content' => 'Содержание',
 
-    /**
-     * Email Content
-     */
+    // Email Content
     'email_action_help' => 'Если у вас возникли проблемы с нажатием кнопки \':actionText\', то скопируйте и вставьте указанный URL-адрес в свой веб-браузер:',
     'email_rights' => 'Все права зарезервированы',
 ];
index ed4fa6704f86116eaecee0a8e653e86b3a88b818..e9481f791466520021fedc76a7aa5593b0bb3752 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => 'Выбрать изображение',
     'image_all' => 'Все',
     'image_all_title' => 'Простмотр всех изображений',
@@ -24,9 +25,7 @@ return [
     'image_delete_success' => 'Изображение успешно удалено',
     'image_upload_remove' => 'Удалить изображение',
 
-    /**
-     * Code editor
-     */
+    // Code Editor
     'code_editor' => 'Изменить код',
     'code_language' => 'Язык кода',
     'code_content' => 'Содержимое кода',
index 448e4e6a12f8b5256308982c385776631de48ba3..475b454a0c4be688cb01f4dbe110b28b92398ded 100644 (file)
@@ -1,14 +1,17 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => 'Недавно созданные',
     'recently_created_pages' => 'Недавно созданные страницы',
     'recently_updated_pages' => 'Недавно обновленные страницы',
     'recently_created_chapters' => 'Недавно созданные главы',
     'recently_created_books' => 'Недавно созданные книги',
+    'recently_created_shelves' => 'Недавно созданные полки',
     'recently_update' => 'Недавно обновленные',
     'recently_viewed' => 'Недавно просмотренные',
     'recent_activity' => 'Недавние действия',
@@ -31,17 +34,13 @@ return [
     'export_pdf' => 'PDF файл',
     'export_text' => 'Текстовый файл',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => 'Разрешения',
     'permissions_intro' => 'После включения эти разрешения будут иметь приоритет над любыми установленными полномочиями.',
     'permissions_enable' => 'Включение пользовательских разрешений',
     'permissions_save' => 'Сохранить разрешения',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => 'Результаты поиска',
     'search_total_results_found' => ':count результатов найдено|:count всего результатов найдено',
     'search_clear' => 'Очистить поиск',
@@ -52,11 +51,13 @@ return [
     'search_content_type' => 'Тип содержимого',
     'search_exact_matches' => 'Точные соответствия',
     'search_tags' => 'Поиск по тегам',
+    'search_options' => 'Параметры',
     'search_viewed_by_me' => 'Просмотрено мной',
     'search_not_viewed_by_me' => 'Не просматривалось мной',
     'search_permissions_set' => 'Набор разрешений',
     'search_created_by_me' => 'Создано мной',
     'search_updated_by_me' => 'Обновлено мной',
+    'search_date_options' => 'Параметры даты',
     'search_updated_before' => 'Обновлено до',
     'search_updated_after' => 'Обновлено после',
     'search_created_before' => 'Создано до',
@@ -64,9 +65,39 @@ return [
     'search_set_date' => 'Установить дату',
     'search_update' => 'Обновить поиск',
 
-    /**
-     * Books
-     */
+    // Shelves
+    'shelf' => 'Полка',
+    'shelves' => 'Полки',
+    'x_shelves' => ':count полок|:count полок',
+    'shelves_long' => 'Книжные полки',
+    'shelves_empty' => 'Полки не созданы',
+    'shelves_create' => 'Создать новую полку',
+    'shelves_popular' => 'Популярные полки',
+    'shelves_new' => 'Новые полки',
+    'shelves_new_action' => 'Новая полка',
+    'shelves_popular_empty' => 'Популярные полки появятся здесь.',
+    'shelves_new_empty' => 'Последние созданные полки появятся здесь.',
+    'shelves_save' => 'Сохранить полку',
+    'shelves_books' => 'Книги из этой полки',
+    'shelves_add_books' => 'Добавить книгу в эту полку',
+    'shelves_drag_books' => 'Перетащите книгу сюда, чтобы добавить на эту полку',
+    'shelves_empty_contents' => 'На этой полке нет книг',
+    'shelves_edit_and_assign' => 'Изменить полку для привязки книг',
+    'shelves_edit_named' => 'Редактировать полку :name',
+    'shelves_edit' => 'Редактировать книжную полку',
+    'shelves_delete' => 'Удалить книжную полку',
+    'shelves_delete_named' => 'Удалить книжную полку :name',
+    'shelves_delete_explain' => "Это приведет к удалению полки с именем ':name'. Привязанные книги удалены не будут.",
+    'shelves_delete_confirmation' => 'Вы уверены, что хотите удалить эту полку?',
+    'shelves_permissions' => 'Доступы к книжной полке',
+    'shelves_permissions_updated' => 'Доступы к книжной полке обновлены',
+    'shelves_permissions_active' => 'Доступы к книжной полке активны',
+    'shelves_copy_permissions_to_books' => 'Наследовать доступы книгам',
+    'shelves_copy_permissions' => 'Копировать доступы',
+    'shelves_copy_permissions_explain' => 'Это применит текущие настройки доступов этой книжной полки ко всем книгам, содержащимся внутри. Перед активацией убедитесь, что все изменения в доступах этой книжной полки сохранены.',
+    'shelves_copy_permission_success' => 'Доступы книжной полки скопированы для :count books',
+
+    // Books
     'book' => 'Книга',
     'books' => 'Книги',
     'x_books' => ':count книга|:count книг',
@@ -74,6 +105,7 @@ return [
     'books_popular' => 'Популярные книги',
     'books_recent' => 'Недавние книги',
     'books_new' => 'Новые книги',
+    'books_new_action' => 'Новая книга',
     'books_popular_empty' => 'Здесь появятся самые популярные книги.',
     'books_new_empty' => 'Здесь появятся самые последние созданные книги.',
     'books_create' => 'Создать новую книгу',
@@ -89,7 +121,6 @@ return [
     'books_permissions_updated' => 'Разрешения на книгу обновлены',
     'books_empty_contents' => 'Для этой книги нет страниц или разделов.',
     'books_empty_create_page' => 'Создать новую страницу',
-    'books_empty_or' => 'или',
     'books_empty_sort_current_book' => 'Сортировка текущей книги',
     'books_empty_add_chapter' => 'Добавить главу',
     'books_permissions_active' => 'действующие разрешения на книгу',
@@ -97,46 +128,15 @@ return [
     'books_navigation' => 'Навигация по книге',
     'books_sort' => 'Сортировка содержимого книги',
     'books_sort_named' => 'Сортировка книги :bookName',
+    'books_sort_name' => 'Сортировать по имени',
+    'books_sort_created' => 'Сортировать по дате создания',
+    'books_sort_updated' => 'Сортировать по дате обновления',
+    'books_sort_chapters_first' => 'Сначала главы',
+    'books_sort_chapters_last' => 'Главы последние',
     'books_sort_show_other' => 'Показать другие книги',
     'books_sort_save' => 'Сохранить новый порядок',
 
-    /**
-     * Shelves
-     */
-    'shelf' => 'Полка',
-    'shelves' => 'Полки',
-    'x_shelves' => ':count полок|:count полок',
-    'shelves_long' => 'Книжные полки',
-    'shelves_empty' => 'Полки не созданы',
-    'shelves_create' => 'Создать новую полку',
-    'shelves_popular' => 'Популярные полки',
-    'shelves_new' => 'Новые полки',
-    'shelves_new_action' => 'Новая полка',
-    'shelves_popular_empty' => 'Популярные полки появятся здесь.',
-    'shelves_new_empty' => 'Последние созданные полки появятся здесь.',
-    'shelves_save' => 'Сохранить полку',
-    'shelves_books' => 'Книги из этой полки',
-    'shelves_add_books' => 'Добавить книгу в эту полку',
-    'shelves_drag_books' => 'Перетащите книгу сюда, чтобы добавить на эту полку',
-    'shelves_empty_contents' => 'На этой полке нет книг',
-    'shelves_edit_and_assign' => 'Изменить полку для привязки книг',
-    'shelves_edit_named' => 'Редактировать полку :name',
-    'shelves_edit' => 'Редактировать книжную полку',
-    'shelves_delete' => 'Удалить книжную полку',
-    'shelves_delete_named' => 'Удалить книжную полку :name',
-    'shelves_delete_explain' => "Это приведет к удалению полки с именем ':name'. Привязанные книги удалены не будут.",
-    'shelves_delete_confirmation' => 'Вы уверены, что хотите удалить эту полку?',
-    'shelves_permissions' => 'Доступы к книжной полке',
-    'shelves_permissions_updated' => 'Доступы к книжной полке обновлены',
-    'shelves_permissions_active' => 'Доступы к книжной полке активны',
-    'shelves_copy_permissions_to_books' => 'Наследовать доступы книгам',
-    'shelves_copy_permissions' => 'Копировать доступы',
-    'shelves_copy_permissions_explain' => 'Это применит текущие настройки доступов этой книжной полки ко всем книгам, содержащимся внутри. Перед активацией убедитесь, что все изменения в доступах этой книжной полки сохранены.',
-    'shelves_copy_permission_success' => 'Доступы книжной полки скопированы для :count books',
-
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => 'Глава',
     'chapters' => 'Главы',
     'x_chapters' => ':count глава|:count главы',
@@ -159,9 +159,7 @@ return [
     'chapters_permissions_success' => 'Разрешения главы обновлены',
     'chapters_search_this' => 'Искать в этой главе',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => 'Страница',
     'pages' => 'Страницы',
     'x_pages' => ':count страница|:count страниц',
@@ -178,7 +176,7 @@ return [
     'pages_delete_confirm' => 'Вы действительно хотите удалить эту страницу?',
     'pages_delete_draft_confirm' => 'Вы действительно хотите удалить этот черновик?',
     'pages_editing_named' => 'Редактирование страницы :pageName',
-    'pages_edit_toggle_header' => 'Переключение заголовка',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'Сохранить черновик',
     'pages_edit_draft' => 'Редактировать черновик',
     'pages_editing_draft' => 'Редактирование черновика',
@@ -212,6 +210,8 @@ return [
     'pages_revisions_created_by' => 'Создана',
     'pages_revisions_date' => 'Дата версии',
     'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Ревизия #:id',
+    'pages_revisions_numbered_changes' => 'Ревизия #:id изменения',
     'pages_revisions_changelog' => 'Список изменений',
     'pages_revisions_changes' => 'Изменения',
     'pages_revisions_current' => 'Текущая версия',
@@ -233,19 +233,21 @@ return [
         'message' => ':start :time. Будьте осторожны, чтобы не перезаписывать друг друга!',
     ],
     'pages_draft_discarded' => 'Черновик сброшен, редактор обновлен текущим содержимым страницы',
+    'pages_specific' => 'Конкретная страница',
+    'pages_is_template' => 'Шаблон страницы',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => 'Теги страницы',
     'chapter_tags' => 'Теги главы',
     'book_tags' => 'Теги книги',
     'shelf_tags' => 'Теги полки',
     'tag' => 'Тег',
     'tags' =>  'Теги',
+    'tag_name' =>  'Имя тега',
     'tag_value' => 'Значение тега (опционально)',
-    'tags_explain' => 'Добавьте теги, чтобы лучше классифицировать ваш контент. \n Вы можете присвоить значение тегу для более глубокой организации.',
+    'tags_explain' => "Добавьте теги, чтобы лучше классифицировать ваш контент. \\n Вы можете присвоить значение тегу для более глубокой организации.",
     'tags_add' => 'Добавить тег',
+    'tags_remove' => 'Удалить этот тэг',
     'attachments' => 'Вложение',
     'attachments_explain' => 'Загрузите несколько файлов или добавьте ссылку для отображения на своей странице. Они видны на боковой панели страницы.',
     'attachments_explain_instant_save' => 'Изменения здесь сохраняются мгновенно.',
@@ -271,19 +273,22 @@ return [
     'attachments_file_uploaded' => 'Файл успешно загружен',
     'attachments_file_updated' => 'Файл успешно обновлен',
     'attachments_link_attached' => 'Ссылка успешно присоединена к странице',
+    'templates' => 'Шаблоны',
+    'templates_set_as_template' => 'Страница это шаблон',
+    'templates_explain_set_as_template' => 'Вы можете назначить эту страницу в качестве шаблона, её содержимое будет использоваться при создании других страниц. Пользователи смогут использовать этот шаблон в случае, если имеют разрешения на просмотр этой страницы.',
+    'templates_replace_content' => 'Заменить содержимое страницы',
+    'templates_append_content' => 'Добавить к содержанию страницы',
+    'templates_prepend_content' => 'Добавить в начало содержимого страницы',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => 'пользователь уже :time',
     'profile_created_content' => 'Созданный контент',
     'profile_not_created_pages' => ':userName не создавал страниц',
     'profile_not_created_chapters' => ':userName не создавал глав',
     'profile_not_created_books' => ':userName не создавал ни одной книги',
+    'profile_not_created_shelves' => ':userName не создал ни одной полки',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => 'Комментарий',
     'comments' => 'Комментарии',
     'comment_add' => 'Комментировать',
@@ -298,13 +303,12 @@ return [
     'comment_deleted_success' => 'Комментарий удален',
     'comment_created_success' => 'Комментарий добавлен',
     'comment_updated_success' => 'Комментарий обновлен',
-    'comment_delete_confirm' => 'Ð\92Ñ\8b Ñ\83веÑ\80енÑ\8b, Ñ\87Ñ\82о Ñ\85оÑ\82иÑ\82е Ñ\83далить этот комментарий?',
+    'comment_delete_confirm' => 'Удалить этот комментарий?',
     'comment_in_reply_to' => 'В ответ на :commentId',
 
-    /**
-     * Revision
-     */
-    'revision_delete_confirm' => 'Вы действительно хотите удалить эту ревизию?',
-    'revision_delete_success' => 'Редактирование удалено',
-    'revision_cannot_delete_latest' => 'Не удается удалить последнюю версию.'
-];
+    // Revision
+    'revision_delete_confirm' => 'Удалить эту ревизию?',
+    'revision_restore_confirm' => 'Восстановить эту ревизию? Текущее содержимое будет заменено.',
+    'revision_delete_success' => 'Ревизия удалена',
+    'revision_cannot_delete_latest' => 'Нельзя удалить последнюю версию.'
+];
\ No newline at end of file
index 6286425a9333e39cdd183f7fd38cb70e57561190..69c63b5244bb813d042f1bb8effdb3c39eca44b7 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => 'У вас нет доступа к запрашиваемой странице.',
     'permissionJson' => 'У вас нет разрешения для запрашиваемого действия.',
@@ -20,7 +18,7 @@ return [
     'ldap_extension_not_installed' => 'LDAP расширения для PHP не установлено',
     'ldap_cannot_connect' => 'Не удается подключиться к серверу ldap, не удалось выполнить начальное соединение',
     'social_no_action_defined' => 'Действие не определено',
-    'social_login_bad_response' => 'При попытке входа с :socialAccount произошла ошибка: \n:error',
+    'social_login_bad_response' => "При попытке входа с :socialAccount произошла ошибка: \\n:error",
     'social_account_in_use' => 'Этот :socialAccount аккаунт уже исопльзуется, попробуйте войти с параметрами :socialAccount.',
     'social_account_email_in_use' => 'Электронный ящик :email уже используется. Если у вас уже есть учетная запись, вы можете подключить свою учетную запись :socialAccount из настроек своего профиля.',
     'social_account_existing' => 'Этот :socialAccount уже привязан к вашему профилю.',
@@ -29,6 +27,7 @@ return [
     'social_account_register_instructions' => 'Если у вас еще нет учетной записи, вы можете зарегистрироваться, используя параметр :socialAccount.',
     'social_driver_not_found' => 'Драйвер для Соцсети не найден',
     'social_driver_not_configured' => 'Настройки вашего :socialAccount заданы неправильно.',
+    'invite_token_expired' => 'Срок действия приглашения истек. Вместо этого вы можете попытаться сбросить пароль своей учетной записи.',
 
     // System
     'path_not_writable' => 'Невозможно загрузить файл по пути :filePath . Убедитесь что сервер доступен для записи.',
@@ -50,6 +49,7 @@ return [
 
     // Entities
     'entity_not_found' => 'Объект не найден',
+    'bookshelf_not_found' => 'Полка не найдена',
     'book_not_found' => 'Книга не найдена',
     'page_not_found' => 'Страница не найдена',
     'chapter_not_found' => 'Глава не найдена',
@@ -65,6 +65,7 @@ return [
     'role_cannot_be_edited' => 'Невозможно отредактировать данную роль',
     'role_system_cannot_be_deleted' => 'Эта роль является системной и не может быть удалена',
     'role_registration_default_cannot_delete' => 'Эта роль не может быть удалена, так как она устанолена в качестве роли по умолчанию',
+    'role_cannot_remove_only_admin' => 'Этот пользователь единственный с правами администратора. Назначьте роль администратора другому пользователю, прежде чем удалить этого.',
 
     // Comments
     'comment_list' => 'При получении комментариев произошла ошибка.',
@@ -80,4 +81,5 @@ return [
     'error_occurred' => 'Произошла ошибка',
     'app_down' => ':appName в данный момент не достпуно',
     'back_soon' => 'Скоро восстановится.',
+
 ];
index 002e4ae28f9715ea98c7b9c6cf5f2d2b793bda49..1a797e36cd6c43b85d51046d29586d4360b477b4 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; Предыдущая',
     'next'     => 'Следующая &raquo;',
 
index 28bc6d7544d438c74476b3e4be804a1eb7699f56..c3324e8de76438aa8a72b81aa7ab8f24f2aea100 100644 (file)
@@ -1,22 +1,15 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
-    'password' => 'Пароль должен содержать не менее шести символов для применения.',
-    'user' => 'Невозможно найти пользователя с указанным email адресом.',
-    'token' => 'Этот токен для сброса пароля недействителен.',
-    'sent' => 'Ссылка для сброса пароля была отправлена на электронную почту!',
+    'password' => 'Пароль должен содержать не менее шести символов и совпадать с подтверждением.',
+    'user' => "Пользователь с указаным email отсутствует.",
+    'token' => 'Токен сброса пароля недействителен.',
+    'sent' => 'Ссылка для сброса пароля отправлена на email!',
     'reset' => 'Ваш пароль был сброшен!',
 
 ];
index 0ff1b969bf3cd61fd1513a10de7168440e96c2ec..f268c7e4ca48e522c803aa1456148c08ad2006c4 100755 (executable)
@@ -13,7 +13,7 @@ return [
 
     // App Settings
     'app_customization' => 'Настройки',
-    'app_features_security' => 'ФÑ\83нкÑ\86ии & Безопасность',
+    'app_features_security' => 'ФÑ\83нкÑ\86ионал & Безопасность',
     'app_name' => 'Имя приложения',
     'app_name_desc' => 'Имя отображается в заголовке email отправленных системой.',
     'app_name_header' => 'Отображать имя приложения в заголовке',
@@ -29,6 +29,7 @@ return [
     'app_editor_desc' => 'Выберите, какой редактор будет использоваться всеми пользователями для редактирования страниц.',
     'app_custom_html' => 'Пользовательский контент заголовка HTML',
     'app_custom_html_desc' => 'Любой контент, добавленный здесь, будет вставлен в нижнюю часть раздела <head> каждой страницы. Это удобно для переопределения стилей или добавления кода аналитики.',
+    'app_custom_html_disabled_notice' => 'Пользовательский контент заголовка HTML отключен на этой странице, чтобы гарантировать отмену любых критических изменений',
     'app_logo' => 'Лого приложения',
     'app_logo_desc' => 'Это изображение должно быть 43px в высоту. <br>Большое изображение будет уменьшено.',
     'app_primary_color' => 'Основной цвет приложения',
@@ -42,13 +43,13 @@ return [
 
     // Registration Settings
     'reg_settings' => 'Настройки регистрации',
-    'reg_enable' => 'РазÑ\80еÑ\88иÑ\82Ñ\8c Ñ\80егиÑ\81Ñ\82Ñ\80аÑ\86иÑ\8fÑ\8e',
+    'reg_enable' => 'Разрешить регистрацию',
     'reg_enable_toggle' => 'Разрешить регистрацию',
-    'reg_enable_desc' => 'Если регистрация разрешена, пользователь сможет зарегистрироваться в системе самомтоятельно. При регистрации назначается роль пользователя по умолчанию',
+    'reg_enable_desc' => 'Если регистрация разрешена, пользователь сможет зарегистрироваться в системе самостоятельно. При регистрации назначается роль пользователя по умолчанию',
     'reg_default_role' => 'Роль пользователя по умолчанию после регистрации',
     'reg_email_confirmation' => 'Подтверждение электонной почты',
     'reg_email_confirmation_toggle' => 'Требовать подтверждение по электронной почте',
-    'reg_confirm_email_desc' => 'Ð\95Ñ\81ли Ð¸Ñ\81полÑ\8cзÑ\83еÑ\82Ñ\81Ñ\8f Ð¾Ð³Ñ\80аниÑ\87ение Ð¿Ð¾ Ð´Ð¾Ð¼ÐµÐ½Ñ\83, Ð¿Ð¾Ð´Ñ\82веÑ\80ждение Ð±Ñ\83деÑ\82 Ð¾Ð±Ñ\8fзаÑ\82елÑ\8cно, Ð° Ñ\8dÑ\82оÑ\82 Ð¿Ñ\83нкÑ\82 Ð¿Ñ\80оигноÑ\80иÑ\80ован.',
+    'reg_confirm_email_desc' => 'Ð\9fÑ\80и Ð¸Ñ\81полÑ\8cзовании Ð¾Ð³Ñ\80аниÑ\87ениÑ\8f Ð¿Ð¾ Ð´Ð¾Ð¼ÐµÐ½Ñ\83 - Ð¿Ð¾Ð´Ñ\82веÑ\80ждение Ð¾Ð±Ñ\8fзаÑ\82елÑ\8cно, Ñ\8dÑ\82оÑ\82 Ð¿Ñ\83нкÑ\82 Ð¸Ð³Ð½Ð¾Ñ\80иÑ\80Ñ\83еÑ\82Ñ\81Ñ\8f.',
     'reg_confirm_restrict_domain' => 'Ограничить регистрацию по домену',
     'reg_confirm_restrict_domain_desc' => 'Введите список доменов почты через запятую, для которых разрешена регистрация. Пользователям будет отправлено письмо для подтверждения адреса перед входом в приложение. <br> Обратите внимание, что пользователи смогут изменить свои адреса уже после регистрации.',
     'reg_confirm_restrict_domain_placeholder' => 'Без ограничений',
@@ -56,7 +57,7 @@ return [
     // Maintenance settings
     'maint' => 'Обслуживание',
     'maint_image_cleanup' => 'Очистка изображений',
-    'maint_image_cleanup_desc' => 'Сканирует содержимое страниц и предыдущих версий и определяет изображения, которые не используются. Убедитесь, что у вас есть резервная копия базы данных и папки изображений перед запуском этой функции.',
+    'maint_image_cleanup_desc' => "Сканирует содержимое страниц и предыдущих версий и определяет изображения, которые не используются. Убедитесь, что у вас есть резервная копия базы данных и папки изображений перед запуском этой функции.",
     'maint_image_cleanup_ignore_revisions' => 'Пропускать изображения в версиях',
     'maint_image_cleanup_run' => 'Выполнить очистку',
     'maint_image_cleanup_warning' => 'Найдено :count возможно бесполезных изображений. Вы уверены, что хотите удалить эти изображения?',
@@ -71,7 +72,7 @@ return [
     'role_delete' => 'Удалить роль',
     'role_delete_confirm' => 'Это удалит роль с именем \':roleName\'.',
     'role_delete_users_assigned' => 'Эта роль назначена :userCount пользователям. Если вы хотите перенести их из этой роли, выберите новую роль ниже.',
-    'role_delete_no_migration' => 'Не мигрировать пользователей',
+    'role_delete_no_migration' => "Не мигрировать пользователей",
     'role_delete_sure' => 'Вы уверены что хотите удалить данную роль?',
     'role_delete_success' => 'Роль успешно удалена',
     'role_edit' => 'Редактировать роль',
@@ -84,6 +85,7 @@ return [
     'role_manage_roles' => 'Управление ролями и правами на роли',
     'role_manage_entity_permissions' => 'Управление правами на все книги, главы и страницы',
     'role_manage_own_entity_permissions' => 'Управление разрешениями для собственных книг, разделов и страниц',
+    'role_manage_page_templates' => 'Управление шаблонами страниц',
     'role_manage_settings' => 'Управление настройками приложения',
     'role_asset' => 'Разрешение для активации',
     'role_asset_desc' => 'Эти разрешения контролируют доступ по умолчанию к параметрам внутри системы. Разрешения на книги, главы и страницы перезапишут эти разрешения.',
@@ -107,7 +109,9 @@ return [
     'users_role' => 'Роли пользователя',
     'users_role_desc' => 'Назначьте роли пользователю. Если назначено несколько ролей, разрешения будут суммироваться и пользователь получит все права назначенных ролей.',
     'users_password' => 'Пароль пользователя',
-    'users_password_desc' => 'Установите пароль для входа в приложение. Должно быть не менее 5 символов.',
+    'users_password_desc' => 'Установите пароль для входа в приложение. Должно быть не менее 6 символов.',
+    'users_send_invite_text' => 'Вы можете отправить этому пользователю email с приглашением, которое позволит ему установить пароль самостоятельно или задайте пароль сами.',
+    'users_send_invite_option' => 'Отправить пользователю email с приглашением.',
     'users_external_auth_id' => 'Внешний ID аутентификации',
     'users_external_auth_id_desc' => 'Этот ID используется для связи с вашей LDAP системой.',
     'users_password_warning' => 'Заполните ниже только если вы хотите сменить свой пароль.',
@@ -125,11 +129,38 @@ return [
     'users_preferred_language' => 'Предпочитаемый язык',
     'users_preferred_language_desc' => 'Этот параметр изменит язык интерфейса приложения. Это не влияет на созданный пользователем контент.',
     'users_social_accounts' => 'Аккаунты Соцсетей',
-    'users_social_accounts_info' => 'Здесь вы можете подключить другие учетные записи для более быстрого и легкого входа в систему. Отключение учетной записи здесь не разрешено. Отменить доступ к настройкам вашего профиля в подключенном социальном аккаунте.',
+    'users_social_accounts_info' => 'Здесь вы можете подключить другие учетные записи для более быстрого и легкого входа в систему. Отключение учетной записи здесь не возможно. Отмените доступ к настройкам вашего профиля в подключенном аккаунте соцсети.',
     'users_social_connect' => 'Подключить аккаунт',
     'users_social_disconnect' => 'Отключить аккаунт',
     'users_social_connected' => ':socialAccount аккаунт упешно подключен к вашему профилю.',
     'users_social_disconnected' => ':socialAccount аккаунт успешно отключен от вашего профиля.',
 
-
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index ae8a3b37eee51829e72c9b45d636e9554367b171..80cd89fd5f8c5feddad658fd1f25787ffd4749bd 100644 (file)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => ':attribute должен быть принят.',
     'active_url'           => ':attribute не является корректным URL.',
     'after'                => ':attribute дата должна быть позже :date.',
@@ -35,12 +30,41 @@ return [
     'digits'               => ':attribute должен состоять из :digits цифр.',
     'digits_between'       => ':attribute должен иметь от :min до :max цифр.',
     'email'                => ':attribute должен быть корректным email адресом.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => ':attribute поле необходимо.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => 'выделенный :attribute некорректен.',
     'image'                => ':attribute должен быть изображением.',
+    'image_extension'      => ':attribute должен быть исправным  и содержать расширение картинки',
     'in'                   => 'выделенный :attribute некорректен.',
     'integer'              => ':attribute должно быть целое число.',
     'ip'                   => ':attribute должен быть корректным IP адресом.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => ':attribute не может быть больше чем :max.',
         'file'    => ':attribute не может быть больше чем :max килобайт.',
@@ -54,7 +78,9 @@ return [
         'string'  => ':attribute должен быть минимум :min символов.',
         'array'   => ':attribute должен содержать хотя бы :min элементов.',
     ],
+    'no_double_extension'  => ':attribute должен иметь только одно расширение файла.',
     'not_in'               => 'Выбранный :attribute некорректен.',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attribute должен быть числом.',
     'regex'                => ':attribute неправильный формат.',
     'required'             => ':attribute обязательное поле.',
@@ -74,35 +100,15 @@ return [
     'timezone'             => ':attribute должен быть корректным часовым поясом.',
     'unique'               => ':attribute уже есть.',
     'url'                  => ':attribute имеет неправильный формат.',
+    'uploaded'             => 'Не удалось загрузить файл. Сервер не может принимать файлы такого размера.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention 'attribute.rule' to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => 'Требуется подтверждение пароля',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of 'email'. This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
index 1d87d3fa35b389fecb756b7e66f0dde5a3aa266d..444d78e95d45586755aecba945a578d8663bd084 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => 'vytvoril stránku',
     'page_create_notification'    => 'Stránka úspešne vytvorená',
@@ -37,4 +35,14 @@ return [
     'book_sort'                   => 'zoradil knihu',
     'book_sort_notification'      => 'Kniha úspešne znovu zoradená',
 
+    // Bookshelves
+    'bookshelf_create'            => 'created Bookshelf',
+    'bookshelf_create_notification'    => 'Bookshelf Successfully Created',
+    'bookshelf_update'                 => 'updated bookshelf',
+    'bookshelf_update_notification'    => 'Bookshelf Successfully Updated',
+    'bookshelf_delete'                 => 'deleted bookshelf',
+    'bookshelf_delete_notification'    => 'Bookshelf Successfully Deleted',
+
+    // Other
+    'commented_on'                => 'commented on',
 ];
index 2fa69ac3e9b88b374495ac860faf75268828b76a..fcce42bbde733d1309a31be2d1908a599a28ed3c 100644 (file)
@@ -1,21 +1,15 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => 'Tieto údaje nesedia s našimi záznamami.',
     'throttle' => 'Priveľa pokusov o prihlásenie. Skúste znova o :seconds sekúnd.',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => 'Registrácia',
     'log_in' => 'Prihlásenie',
     'log_in_with' => 'Prihlásiť sa cez :socialDriver',
@@ -27,11 +21,13 @@ return [
     'email' => 'Email',
     'password' => 'Heslo',
     'password_confirm' => 'Potvrdiť heslo',
-    'password_hint' => 'Musí mať viac ako 5 znakov',
+    'password_hint' => 'Musí mať viac ako 7 znakov',
     'forgot_password' => 'Zabudli ste heslo?',
     'remember_me' => 'Zapamätať si ma',
     'ldap_email_hint' => 'Zadajte prosím email, ktorý sa má použiť pre tento účet.',
     'create_account' => 'Vytvoriť účet',
+    'already_have_account' => 'Already have an account?',
+    'dont_have_account' => 'Don\'t have an account?',
     'social_login' => 'Sociálne prihlásenie',
     'social_registration' => 'Sociálna registrácia',
     'social_registration_text' => 'Registrovať sa a prihlásiť sa použitím inej služby.',
@@ -43,23 +39,18 @@ return [
     'register_success' => 'Ďakujeme za registráciu! Teraz ste registrovaný a prihlásený.',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => 'Reset hesla',
     'reset_password_send_instructions' => 'Zadajte svoj email nižšie a bude Vám odoslaný email s odkazom pre reset hesla.',
     'reset_password_send_button' => 'Poslať odkaz na reset hesla',
     'reset_password_sent_success' => 'Odkaz na reset hesla bol poslaný na :email.',
     'reset_password_success' => 'Vaše heslo bolo úspešne resetované.',
-
     'email_reset_subject' => 'Reset Vášho :appName hesla',
     'email_reset_text' => 'Tento email Ste dostali pretože sme dostali požiadavku na reset hesla pre Váš účet.',
     'email_reset_not_requested' => 'Ak ste nepožiadali o reset hesla, nemusíte nič robiť.',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => 'Potvrdiť email na :appName',
     'email_confirm_greeting' => 'Ďakujeme za pridanie sa k :appName!',
     'email_confirm_text' => 'Prosím potvrďte Vašu emailovú adresu kliknutím na tlačidlo nižšie:',
@@ -73,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Prosím, kliknite na odkaz v emaili, ktorý bol poslaný krátko po Vašej registrácii.',
     'email_not_confirmed_resend' => 'Ak nemôžete násť email, môžete znova odoslať overovací email odoslaním doleuvedeného formulára.',
     'email_not_confirmed_resend_button' => 'Znova odoslať overovací email',
-];
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
+];
\ No newline at end of file
index a823fb37915c9d0f62bfe26e091833300184aa57..1f915c71e7d24dfdbc15863ecc21a70316a76b53 100644 (file)
@@ -1,60 +1,76 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => 'Zrušiť',
     'confirm' => 'Potvrdiť',
     'back' => 'Späť',
     'save' => 'Uložiť',
     'continue' => 'Pokračovať',
     'select' => 'Vybrať',
+    'toggle_all' => 'Toggle All',
+    'more' => 'More',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => 'Meno',
     'description' => 'Popis',
     'role' => 'Rola',
     'cover_image' => 'Obal knihy',
     'cover_image_description' => 'Tento obrázok by mal byť približne 300 x 170 pixelov.',
-    /**
-     * Actions
-     */
+    
+    // Actions
     'actions' => 'Akcie',
     'view' => 'Zobraziť',
+    'view_all' => 'View All',
     'create' => 'Vytvoriť',
     'update' => 'Aktualizovať',
     'edit' => 'Editovať',
     'sort' => 'Zoradiť',
     'move' => 'Presunúť',
+    'copy' => 'Copy',
+    'reply' => 'Reply',
     'delete' => 'Zmazať',
     'search' => 'Hľadť',
     'search_clear' => 'Vyčistiť hľadanie',
     'reset' => 'Reset',
     'remove' => 'Odstrániť',
+    'add' => 'Add',
 
+    // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
+    'sort_name' => 'Name',
+    'sort_created_at' => 'Created Date',
+    'sort_updated_at' => 'Updated Date',
 
-    /**
-     * Misc
-     */
+    // Misc
     'deleted_user' => 'Odstránený používateľ',
     'no_activity' => 'Žiadna aktivita na zobrazenie',
     'no_items' => 'Žiadne položky nie sú dostupné',
     'back_to_top' => 'Späť nahor',
     'toggle_details' => 'Prepnúť detaily',
     'toggle_thumbnails' => 'Prepnúť náhľady',
+    'details' => 'Details',
+    'grid_view' => 'Grid View',
+    'list_view' => 'List View',
+    'default' => 'Default',
+    'breadcrumb' => 'Breadcrumb',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => 'Zobraziť profil',
     'edit_profile' => 'Upraviť profil',
 
-    /**
-     * Email Content
-     */
+    // Layout tabs
+    'tab_info' => 'Info',
+    'tab_content' => 'Content',
+
+    // Email Content
     'email_action_help' => 'Ak máte problém klinkúť na tlačidlo ":actionText", skopírujte a vložte URL uvedenú nižšie do Vášho prehliadača:',
     'email_rights' => 'Všetky práva vyhradené',
 ];
index c20b62c7afb4c51645bf12333cb39642e9b8e7aa..726d8361438ec26712eaaf5611b8de49868a16b7 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => 'Vybrať obrázok',
     'image_all' => 'Všetko',
     'image_all_title' => 'Zobraziť všetky obrázky',
@@ -21,5 +22,12 @@ return [
     'image_preview' => 'Náhľad obrázka',
     'image_upload_success' => 'Obrázok úspešne nahraný',
     'image_update_success' => 'Detaily obrázka úspešne aktualizované',
-    'image_delete_success' => 'Obrázok úspešne zmazaný'
+    'image_delete_success' => 'Obrázok úspešne zmazaný',
+    'image_upload_remove' => 'Remove',
+
+    // Code Editor
+    'code_editor' => 'Edit Code',
+    'code_language' => 'Code Language',
+    'code_content' => 'Code Content',
+    'code_save' => 'Save Code',
 ];
index 7fbbaf2e2676ed0c0b62477ca8c05cdb02a3aefd..2b42ca86cd43c89238b18d808ad1411c4807b8dd 100644 (file)
@@ -1,24 +1,27 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => 'Nedávno vytvorené',
     'recently_created_pages' => 'Nedávno vytvorené stránky',
     'recently_updated_pages' => 'Nedávno aktualizované stránky',
     'recently_created_chapters' => 'Nedávno vytvorené kapitoly',
     'recently_created_books' => 'Nedávno vytvorené knihy',
+    'recently_created_shelves' => 'Recently Created Shelves',
     'recently_update' => 'Nedávno aktualizované',
     'recently_viewed' => 'Nedávno zobrazené',
     'recent_activity' => 'Nedávna aktivita',
     'create_now' => 'Vytvoriť teraz',
     'revisions' => 'Revízie',
+    'meta_revision' => 'Revision #:revisionCount',
     'meta_created' => 'Vytvorené :timeLength',
     'meta_created_name' => 'Vytvorené :timeLength používateľom :user',
     'meta_updated' => 'Aktualizované :timeLength',
     'meta_updated_name' => 'Aktualizované :timeLength používateľom :user',
-    'x_pages' => ':count stránok',
     'entity_select' => 'Entita vybraná',
     'images' => 'Obrázky',
     'my_recent_drafts' => 'Moje nedávne koncepty',
@@ -31,40 +34,80 @@ return [
     'export_pdf' => 'PDF súbor',
     'export_text' => 'Súbor s čistým textom',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => 'Oprávnenia',
     'permissions_intro' => 'Ak budú tieto oprávnenia povolené, budú mať prioritu pred oprávneniami roly.',
     'permissions_enable' => 'Povoliť vlastné oprávnenia',
     'permissions_save' => 'Uložiť oprávnenia',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => 'Výsledky hľadania',
-    'search_results_page' => 'Výsledky hľadania stránky',
-    'search_results_chapter' => 'Výsledky hľadania kapitoly',
-    'search_results_book' => 'Výsledky hľadania knihy',
+    'search_total_results_found' => ':count result found|:count total results found',
     'search_clear' => 'Vyčistiť hľadanie',
-    'search_view_pages' => 'Zobraziť všetky vyhovujúce stránky',
-    'search_view_chapters' => 'Zobraziť všetky vyhovujúce kapitoly',
-    'search_view_books' => 'Zobraziť všetky vyhovujúce knihy',
     'search_no_pages' => 'Žiadne stránky nevyhovujú tomuto hľadaniu',
     'search_for_term' => 'Hľadať :term',
-    'search_page_for_term' => 'Hľadať :term medzi stránkami',
-    'search_chapter_for_term' => 'Hľadať :term medzi kapitolami',
-    'search_book_for_term' => 'Hľadať :term medzi knihami',
+    'search_more' => 'More Results',
+    'search_filters' => 'Search Filters',
+    'search_content_type' => 'Content Type',
+    'search_exact_matches' => 'Exact Matches',
+    'search_tags' => 'Tag Searches',
+    'search_options' => 'Options',
+    'search_viewed_by_me' => 'Viewed by me',
+    'search_not_viewed_by_me' => 'Not viewed by me',
+    'search_permissions_set' => 'Permissions set',
+    'search_created_by_me' => 'Created by me',
+    'search_updated_by_me' => 'Updated by me',
+    'search_date_options' => 'Date Options',
+    'search_updated_before' => 'Updated before',
+    'search_updated_after' => 'Updated after',
+    'search_created_before' => 'Created before',
+    'search_created_after' => 'Created after',
+    'search_set_date' => 'Set Date',
+    'search_update' => 'Update Search',
+
+    // Shelves
+    'shelf' => 'Shelf',
+    'shelves' => 'Shelves',
+    'x_shelves' => ':count Shelf|:count Shelves',
+    'shelves_long' => 'Bookshelves',
+    'shelves_empty' => 'No shelves have been created',
+    'shelves_create' => 'Create New Shelf',
+    'shelves_popular' => 'Popular Shelves',
+    'shelves_new' => 'New Shelves',
+    'shelves_new_action' => 'New Shelf',
+    'shelves_popular_empty' => 'The most popular shelves will appear here.',
+    'shelves_new_empty' => 'The most recently created shelves will appear here.',
+    'shelves_save' => 'Save Shelf',
+    'shelves_books' => 'Books on this shelf',
+    'shelves_add_books' => 'Add books to this shelf',
+    'shelves_drag_books' => 'Drag books here to add them to this shelf',
+    'shelves_empty_contents' => 'This shelf has no books assigned to it',
+    'shelves_edit_and_assign' => 'Edit shelf to assign books',
+    'shelves_edit_named' => 'Edit Bookshelf :name',
+    'shelves_edit' => 'Edit Bookshelf',
+    'shelves_delete' => 'Delete Bookshelf',
+    'shelves_delete_named' => 'Delete Bookshelf :name',
+    'shelves_delete_explain' => "This will delete the bookshelf with the name ':name'. Contained books will not be deleted.",
+    'shelves_delete_confirmation' => 'Are you sure you want to delete this bookshelf?',
+    'shelves_permissions' => 'Bookshelf Permissions',
+    'shelves_permissions_updated' => 'Bookshelf Permissions Updated',
+    'shelves_permissions_active' => 'Bookshelf Permissions Active',
+    'shelves_copy_permissions_to_books' => 'Copy Permissions to Books',
+    'shelves_copy_permissions' => 'Copy Permissions',
+    'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this bookshelf to all books contained within. Before activating, ensure any changes to the permissions of this bookshelf have been saved.',
+    'shelves_copy_permission_success' => 'Bookshelf permissions copied to :count books',
 
-    /**
-     * Books
-     */
+    // Books
     'book' => 'Kniha',
     'books' => 'Knihy',
+    'x_books' => ':count Book|:count Books',
     'books_empty' => 'Žiadne knihy neboli vytvorené',
     'books_popular' => 'Populárne knihy',
     'books_recent' => 'Nedávne knihy',
+    'books_new' => 'New Books',
+    'books_new_action' => 'New Book',
     'books_popular_empty' => 'Najpopulárnejšie knihy sa objavia tu.',
+    'books_new_empty' => 'The most recently created books will appear here.',
     'books_create' => 'Vytvoriť novú knihu',
     'books_delete' => 'Zmazať knihu',
     'books_delete_named' => 'Zmazať knihu :bookName',
@@ -78,7 +121,6 @@ return [
     'books_permissions_updated' => 'Oprávnenia knihy aktualizované',
     'books_empty_contents' => 'Pre túto knihu neboli vytvorené žiadne stránky alebo kapitoly.',
     'books_empty_create_page' => 'Vytvoriť novú stránku',
-    'books_empty_or' => 'alebo',
     'books_empty_sort_current_book' => 'Zoradiť aktuálnu knihu',
     'books_empty_add_chapter' => 'Pridať kapitolu',
     'books_permissions_active' => 'Oprávnenia knihy aktívne',
@@ -86,14 +128,18 @@ return [
     'books_navigation' => 'Navigácia knihy',
     'books_sort' => 'Zoradiť obsah knihy',
     'books_sort_named' => 'Zoradiť knihu :bookName',
+    'books_sort_name' => 'Sort by Name',
+    'books_sort_created' => 'Sort by Created Date',
+    'books_sort_updated' => 'Sort by Updated Date',
+    'books_sort_chapters_first' => 'Chapters First',
+    'books_sort_chapters_last' => 'Chapters Last',
     'books_sort_show_other' => 'Zobraziť ostatné knihy',
     'books_sort_save' => 'Uložiť nové zoradenie',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => 'Kapitola',
     'chapters' => 'Kapitoly',
+    'x_chapters' => ':count Chapter|:count Chapters',
     'chapters_popular' => 'Populárne kapitoly',
     'chapters_new' => 'Nová kapitola',
     'chapters_create' => 'Vytvoriť novú kapitolu',
@@ -112,12 +158,12 @@ return [
     'chapters_empty' => 'V tejto kapitole nie sú teraz žiadne stránky.',
     'chapters_permissions_active' => 'Oprávnenia kapitoly aktívne',
     'chapters_permissions_success' => 'Oprávnenia kapitoly aktualizované',
+    'chapters_search_this' => 'Search this chapter',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => 'Stránka',
     'pages' => 'Stránky',
+    'x_pages' => ':count stránok',
     'pages_popular' => 'Populárne stránky',
     'pages_new' => 'Nová stránka',
     'pages_attachments' => 'Prílohy',
@@ -131,7 +177,7 @@ return [
     'pages_delete_confirm' => 'Ste si istý, že chcete zmazať túto stránku?',
     'pages_delete_draft_confirm' => 'Ste si istý, že chcete zmazať tento koncept stránky?',
     'pages_editing_named' => 'Upraviť stránku :pageName',
-    'pages_edit_toggle_header' => 'Prepnúť hlavičku',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'Uložiť koncept',
     'pages_edit_draft' => 'Upraviť koncept stránky',
     'pages_editing_draft' => 'Upravuje sa koncept',
@@ -149,16 +195,24 @@ return [
     'pages_md_preview' => 'Náhľad',
     'pages_md_insert_image' => 'Vložiť obrázok',
     'pages_md_insert_link' => 'Vložiť odkaz na entitu',
+    'pages_md_insert_drawing' => 'Insert Drawing',
     'pages_not_in_chapter' => 'Stránka nie je v kapitole',
     'pages_move' => 'Presunúť stránku',
     'pages_move_success' => 'Stránka presunutá do ":parentName"',
+    'pages_copy' => 'Copy Page',
+    'pages_copy_desination' => 'Copy Destination',
+    'pages_copy_success' => 'Page successfully copied',
     'pages_permissions' => 'Oprávnenia stránky',
     'pages_permissions_success' => 'Oprávnenia stránky aktualizované',
+    'pages_revision' => 'Revision',
     'pages_revisions' => 'Revízie stránky',
     'pages_revisions_named' => 'Revízie stránky :pageName',
     'pages_revision_named' => 'Revízia stránky :pageName',
     'pages_revisions_created_by' => 'Vytvoril',
     'pages_revisions_date' => 'Dátum revízie',
+    'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
     'pages_revisions_changelog' => 'Záznam zmien',
     'pages_revisions_changes' => 'Zmeny',
     'pages_revisions_current' => 'Aktuálna verzia',
@@ -180,16 +234,21 @@ return [
         'message' => ':start :time. Dávajte pozor aby ste si navzájom neprepísali zmeny!',
     ],
     'pages_draft_discarded' => 'Koncept ostránený, aktuálny obsah stránky bol nahraný do editora',
+    'pages_specific' => 'Specific Page',
+    'pages_is_template' => 'Page Template',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => 'Štítky stránok',
+    'chapter_tags' => 'Chapter Tags',
+    'book_tags' => 'Book Tags',
+    'shelf_tags' => 'Shelf Tags',
     'tag' => 'Štítok',
     'tags' =>  'Štítky',
+    'tag_name' =>  'Tag Name',
     'tag_value' => 'Hodnota štítku (Voliteľné)',
     'tags_explain' => "Pridajte pár štítkov pre uľahčenie kategorizácie Vášho obsahu. \n Štítku môžete priradiť hodnotu pre ešte lepšiu organizáciu.",
     'tags_add' => 'Pridať ďalší štítok',
+    'tags_remove' => 'Remove this tag',
     'attachments' => 'Prílohy',
     'attachments_explain' => 'Nahrajte nejaké súbory alebo priložte zopár odkazov pre zobrazenie na Vašej stránke. Budú viditeľné v bočnom paneli.',
     'attachments_explain_instant_save' => 'Zmeny budú okamžite uložené.',
@@ -215,28 +274,42 @@ return [
     'attachments_file_uploaded' => 'Súbor úspešne nahraný',
     'attachments_file_updated' => 'Súbor úspešne aktualizovaný',
     'attachments_link_attached' => 'Odkaz úspešne pripojený k stránke',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => 'Používateľ už :time',
     'profile_created_content' => 'Vytvorený obsah',
     'profile_not_created_pages' => ':userName nevytvoril žiadne stránky',
     'profile_not_created_chapters' => ':userName nevytvoril žiadne kapitoly',
     'profile_not_created_books' => ':userName nevytvoril žiadne knihy',
+    'profile_not_created_shelves' => ':userName has not created any shelves',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => 'Komentár',
     'comments' => 'Komentáre',
+    'comment_add' => 'Add Comment',
     'comment_placeholder' => 'Tu zadajte svoje pripomienky',
+    'comment_count' => '{0} No Comments|{1} 1 Comment|[2,*] :count Comments',
     'comment_save' => 'Uložiť komentár',
+    'comment_saving' => 'Saving comment...',
+    'comment_deleting' => 'Deleting comment...',
+    'comment_new' => 'New Comment',
+    'comment_created' => 'commented :createDiff',
+    'comment_updated' => 'Updated :updateDiff by :username',
+    'comment_deleted_success' => 'Comment deleted',
+    'comment_created_success' => 'Comment added',
+    'comment_updated_success' => 'Comment updated',
+    'comment_delete_confirm' => 'Are you sure you want to delete this comment?',
+    'comment_in_reply_to' => 'In reply to :commentId',
 
-    /**
-     * Revision
-     */
+    // Revision
     'revision_delete_confirm' => 'Naozaj chcete túto revíziu odstrániť?',
+    'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
     'revision_delete_success' => 'Revízia bola vymazaná',
     'revision_cannot_delete_latest' => 'Nie je možné vymazať poslednú revíziu.'
-];
+];
\ No newline at end of file
index 05277014be1bb056e5b37de664b1bc1d42b23b74..361711d9aae29f1c66264d33bfcc018310b858ef 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => 'Nemáte oprávnenie pre prístup k požadovanej stránke.',
     'permissionJson' => 'Nemáte oprávnenie pre vykonanie požadovaného úkonu.',
@@ -20,6 +18,7 @@ return [
     'ldap_extension_not_installed' => 'LDAP PHP extension not installed',
     'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed',
     'social_no_action_defined' => 'Nebola definovaná žiadna akcia',
+    'social_login_bad_response' => "Error received during :socialAccount login: \n:error",
     'social_account_in_use' => 'Tento :socialAccount účet sa už používa, skúste sa prihlásiť pomocou možnosti :socialAccount.',
     'social_account_email_in_use' => 'Email :email sa už používa. Ak už máte účet, môžete pripojiť svoj :socialAccount účet v nastaveniach profilu.',
     'social_account_existing' => 'Tento :socialAccount účet je už spojený s Vaším profilom.',
@@ -28,23 +27,29 @@ return [
     'social_account_register_instructions' => 'Ak zatiaľ nemáte účet, môžete sa registrovať pomocou možnosti :socialAccount.',
     'social_driver_not_found' => 'Ovládač socialnych sietí nebol nájdený',
     'social_driver_not_configured' => 'Nastavenia Vášho :socialAccount účtu nie sú správne.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => 'Do cesty :filePath sa nedá nahrávať. Uistite sa, že je zapisovateľná serverom.',
     'cannot_get_image_from_url' => 'Nedá sa získať obrázok z :url',
     'cannot_create_thumbs' => 'Server nedokáže vytvoriť náhľady. Skontrolujte prosím, či máte nainštalované GD rozšírenie PHP.',
     'server_upload_limit' => 'Server nedovoľuje nahrávanie súborov s takouto veľkosťou. Skúste prosím menší súbor.',
+    'uploaded'  => 'The server does not allow uploads of this size. Please try a smaller file size.',
     'image_upload_error' => 'Pri nahrávaní obrázka nastala chyba',
+    'image_upload_type_error' => 'The image type being uploaded is invalid',
     'file_upload_timeout' => 'Nahrávanie súboru vypršalo.',
 
     // Attachments
     'attachment_page_mismatch' => 'Page mismatch during attachment update',
+    'attachment_not_found' => 'Attachment not found',
 
     // Pages
     'page_draft_autosave_fail' => 'Koncept nemohol byť uložený. Uistite sa, že máte pripojenie k internetu pre uložením tejto stránky',
+    'page_custom_home_deletion' => 'Cannot delete a page while it is set as a homepage',
 
     // Entities
     'entity_not_found' => 'Entita nenájdená',
+    'bookshelf_not_found' => 'Bookshelf not found',
     'book_not_found' => 'Kniha nenájdená',
     'page_not_found' => 'Stránka nenájdená',
     'chapter_not_found' => 'Kapitola nenájdená',
@@ -60,6 +65,14 @@ return [
     'role_cannot_be_edited' => 'Táto rola nemôže byť upravovaná',
     'role_system_cannot_be_deleted' => 'Táto rola je systémová rola a nemôže byť zmazaná',
     'role_registration_default_cannot_delete' => 'Táto rola nemôže byť zmazaná, pretože je nastavená ako prednastavená rola pri registrácii',
+    'role_cannot_remove_only_admin' => 'This user is the only user assigned to the administrator role. Assign the administrator role to another user before attempting to remove it here.',
+
+    // Comments
+    'comment_list' => 'Pri načítaní komentárov sa vyskytla chyba',
+    'cannot_add_comment_to_draft' => 'Do konceptu nemôžete pridávať komentáre.',
+    'comment_add' => 'Počas pridávania komentára sa vyskytla chyba',
+    'comment_delete' => 'Pri odstraňovaní komentára došlo k chybe',
+    'empty_comment' => 'Nelze pridať prázdny komentár.',
 
     // Error pages
     '404_page_not_found' => 'Stránka nenájdená',
@@ -69,10 +82,4 @@ return [
     'app_down' => ':appName je momentálne nedostupná',
     'back_soon' => 'Čoskoro bude opäť dostupná.',
 
-    // comments
-    'comment_list' => 'Pri načítaní komentárov sa vyskytla chyba',
-    'cannot_add_comment_to_draft' => 'Do konceptu nemôžete pridávať komentáre.',
-    'comment_add' => 'Počas pridávania komentára sa vyskytla chyba',
-    'comment_delete' => 'Pri odstraňovaní komentára došlo k chybe',
-    'empty_comment' => 'Nelze pridať prázdny komentár.',
 ];
index 8f844f5f487bf37f372b681e16ce8a84414e9ed3..7c9333efae86c1381a7650e817f2fbbc05d8ea87 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; Predchádzajúca',
     'next'     => 'Ďalšia &raquo;',
 
index ff2eb68fa2df3b95c66e12a4e6b383159c02afa0..de7d24442212948ca4ec3009b07dd89fa555f6b1 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => 'Heslo musí obsahovať aspoň šesť znakov a musí byť rovnaké ako potvrdzujúce.',
     'user' => "Nenašli sme používateľa s takou emailovou adresou.",
     'token' => 'Tento token pre reset hesla je neplatný.',
index 521af196e5b4458b168df6b9c1538491ceedb59c..dc1e06bc58f01dd82d7bef8ab78c9757632d5009 100644 (file)
@@ -1,56 +1,70 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
-
+    // Common Messages
     'settings' => 'Nastavenia',
     'settings_save' => 'Uložiť nastavenia',
     'settings_save_success' => 'Nastavenia uložené',
 
-    /**
-     * App settings
-     */
-
-    'app_settings' => 'Nastavenia aplikácie',
+    // App Settings
+    'app_customization' => 'Customization',
+    'app_features_security' => 'Features & Security',
     'app_name' => 'Názov aplikácia',
     'app_name_desc' => 'Tento názov sa zobrazuje v hlavičke a v emailoch.',
     'app_name_header' => 'Zobraziť názov aplikácie v hlavičke?',
+    'app_public_access' => 'Public Access',
+    'app_public_access_desc' => 'Enabling this option will allow visitors, that are not logged-in, to access content in your BookStack instance.',
+    'app_public_access_desc_guest' => 'Access for public visitors can be controlled through the "Guest" user.',
+    'app_public_access_toggle' => 'Allow public access',
     'app_public_viewing' => 'Povoliť verejné zobrazenie?',
     'app_secure_images' => 'Povoliť nahrávanie súborov so zvýšeným zabezpečením?',
+    'app_secure_images_toggle' => 'Enable higher security image uploads',
     'app_secure_images_desc' => 'Kvôli výkonu sú všetky obrázky verejné. Táto možnosť pridá pred URL obrázka náhodný, ťažko uhádnuteľný reťazec. Aby ste zabránili jednoduchému prístupu, uistite sa, že indexy priečinkov nie sú povolené.',
     'app_editor' => 'Editor stránky',
     'app_editor_desc' => 'Vyberte editor, ktorý bude používaný všetkými používateľmi na editáciu stránok.',
     'app_custom_html' => 'Vlastný HTML obsah hlavičky',
     'app_custom_html_desc' => 'Všetok text pridaný sem bude vložený naspodok <head> sekcie na každej stránke. Môže sa to zísť pri zmene štýlu alebo pre pridanie analytického kódu.',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'Logo aplikácie',
     'app_logo_desc' => 'Tento obrázok by mal mať 43px na výšku. <br>Veľké obrázky budú preškálované na menší rozmer.',
     'app_primary_color' => 'Primárna farba pre aplikáciu',
     'app_primary_color_desc' => 'Toto by mala byť hodnota v hex tvare. <br>Nechajte prázdne ak chcete použiť prednastavenú farbu.',
+    'app_homepage' => 'Application Homepage',
+    'app_homepage_desc' => 'Select a view to show on the homepage instead of the default view. Page permissions are ignored for selected pages.',
+    'app_homepage_select' => 'Select a page',
     'app_disable_comments' => 'Zakázať komentáre',
+    'app_disable_comments_toggle' => 'Disable comments',
     'app_disable_comments_desc' => 'Zakázať komentáre na všetkých stránkach aplikácie. Existujúce komentáre sa nezobrazujú.',
 
-    /**
-     * Registration settings
-     */
-
+    // Registration Settings
     'reg_settings' => 'Nastavenia registrácie',
-    'reg_allow' => 'Povoliť registráciu?',
+    'reg_enable' => 'Enable Registration',
+    'reg_enable_toggle' => 'Enable registration',
+    'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
     'reg_default_role' => 'Prednastavená používateľská rola po registrácii',
-    'reg_confirm_email' => 'Vyžadovať overenie emailu?',
+    'reg_email_confirmation' => 'Email Confirmation',
+    'reg_email_confirmation_toggle' => 'Require email confirmation',
     'reg_confirm_email_desc' => 'Ak je použité obmedzenie domény, potom bude vyžadované overenie emailu a hodnota nižšie bude ignorovaná.',
     'reg_confirm_restrict_domain' => 'Obmedziť registráciu na doménu',
     'reg_confirm_restrict_domain_desc' => 'Zadajte zoznam domén, pre ktoré chcete povoliť registráciu oddelených čiarkou. Používatelia dostanú email kvôli overeniu adresy predtým ako im bude dovolené používať aplikáciu. <br> Používatelia si budú môcť po úspešnej registrácii zmeniť svoju emailovú adresu.',
     'reg_confirm_restrict_domain_placeholder' => 'Nie sú nastavené žiadne obmedzenia',
 
-    /**
-     * Role settings
-     */
+    // Maintenance settings
+    'maint' => 'Maintenance',
+    'maint_image_cleanup' => 'Cleanup Images',
+    'maint_image_cleanup_desc' => "Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.",
+    'maint_image_cleanup_ignore_revisions' => 'Ignore images in revisions',
+    'maint_image_cleanup_run' => 'Run Cleanup',
+    'maint_image_cleanup_warning' => ':count potentially unused images were found. Are you sure you want to delete these images?',
+    'maint_image_cleanup_success' => ':count potentially unused images found and deleted!',
+    'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!',
 
+    // Role Settings
     'roles' => 'Roly',
     'role_user_roles' => 'Používateľské roly',
     'role_create' => 'Vytvoriť novú rolu',
@@ -65,14 +79,17 @@ return [
     'role_details' => 'Detaily roly',
     'role_name' => 'Názov roly',
     'role_desc' => 'Krátky popis roly',
+    'role_external_auth_id' => 'External Authentication IDs',
     'role_system' => 'Systémové oprávnenia',
     'role_manage_users' => 'Spravovať používateľov',
     'role_manage_roles' => 'Spravovať role a oprávnenia rolí',
     'role_manage_entity_permissions' => 'Spravovať všetky oprávnenia kníh, kapitol a stránok',
     'role_manage_own_entity_permissions' => 'Spravovať oprávnenia vlastných kníh, kapitol a stránok',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'Spravovať nastavenia aplikácie',
     'role_asset' => 'Oprávnenia majetku',
     'role_asset_desc' => 'Tieto oprávnenia regulujú prednastavený prístup k zdroju v systéme. Oprávnenia pre knihy, kapitoly a stránky majú vyššiu prioritu.',
+    'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.',
     'role_all' => 'Všetko',
     'role_own' => 'Vlastné',
     'role_controlled_by_asset' => 'Regulované zdrojom, do ktorého sú nahrané',
@@ -81,19 +98,24 @@ return [
     'role_users' => 'Používatelia s touto rolou',
     'role_users_none' => 'Žiadni používatelia nemajú priradenú túto rolu',
 
-    /**
-     * Users
-     */
-
+    // Users
     'users' => 'Používatelia',
     'user_profile' => 'Profil používateľa',
     'users_add_new' => 'Pridať nového používateľa',
     'users_search' => 'Hľadať medzi používateľmi',
+    'users_details' => 'User Details',
+    'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.',
+    'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.',
     'users_role' => 'Používateľské roly',
+    'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
+    'users_password' => 'User Password',
+    'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 6 characters long.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => 'Externé autentifikačné ID',
+    'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your LDAP system.',
     'users_password_warning' => 'Pole nižšie vyplňte iba ak chcete zmeniť heslo:',
     'users_system_public' => 'Tento účet reprezentuje každého hosťovského používateľa, ktorý navštívi Vašu inštanciu. Nedá sa pomocou neho prihlásiť a je priradený automaticky.',
-    'users_books_view_type' => 'Preferované rozloženie pre prezeranie kníh',
     'users_delete' => 'Zmazať používateľa',
     'users_delete_named' => 'Zmazať používateľa :userName',
     'users_delete_warning' => ' Toto úplne odstráni používateľa menom \':userName\' zo systému.',
@@ -105,10 +127,40 @@ return [
     'users_avatar' => 'Avatar používateľa',
     'users_avatar_desc' => 'Tento obrázok by mal byť štvorec s rozmerom približne 256px.',
     'users_preferred_language' => 'Preferovaný jazyk',
+    'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
     'users_social_accounts' => 'Sociálne účty',
     'users_social_accounts_info' => 'Tu si môžete pripojiť iné účty pre rýchlejšie a jednoduchšie prihlásenie. Disconnecting an account here does not previously authorized access. Revoke access from your profile settings on the connected social account.',
     'users_social_connect' => 'Pripojiť účet',
     'users_social_disconnect' => 'Odpojiť účet',
     'users_social_connected' => ':socialAccount účet bol úspešne pripojený k Vášmu profilu.',
     'users_social_disconnected' => ':socialAccount účet bol úspešne odpojený od Vášho profilu.',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index b365b409d0fd2acf81c0f0d7df75c782bb2cddb0..8dc26905dc4b5ee8c606385cc40dd876a7ceb992 100644 (file)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => ':attribute musí byť akceptovaný.',
     'active_url'           => ':attribute nie je platná URL.',
     'after'                => ':attribute musí byť dátum po :date.',
@@ -35,12 +30,41 @@ return [
     'digits'               => ':attribute musí mať :digits číslic.',
     'digits_between'       => ':attribute musí mať medzi :min a :max číslicami.',
     'email'                => ':attribute musí byť platná emailová adresa.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => 'Políčko :attribute je povinné.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => 'Vybraný :attribute nie je platný.',
     'image'                => ':attribute musí byť obrázok.',
+    'image_extension'      => 'The :attribute must have a valid & supported image extension.',
     'in'                   => 'Vybraný :attribute je neplatný.',
     'integer'              => ':attribute musí byť celé číslo.',
     'ip'                   => ':attribute musí byť platná IP adresa.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => ':attribute nesmie byť väčší ako :max.',
         'file'    => ':attribute nesmie byť väčší ako :max kilobajtov.',
@@ -54,7 +78,9 @@ return [
         'string'  => ':attribute musí mať aspoň :min znakov.',
         'array'   => ':attribute musí mať aspoň :min položiek.',
     ],
+    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => 'Vybraný :attribute je neplatný.',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attribute musí byť číslo.',
     'regex'                => ':attribute formát je neplatný.',
     'required'             => 'Políčko :attribute je povinné.',
@@ -74,35 +100,15 @@ return [
     'timezone'             => ':attribute musí byť plantá časová zóna.',
     'unique'               => ':attribute je už použité.',
     'url'                  => ':attribute formát je neplatný.',
+    'uploaded'             => 'The file could not be uploaded. The server may not accept files of this size.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => 'Vyžaduje sa potvrdenie hesla',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
index 6730d5f98c92b08baf47735bee7f2776828967f4..1cb8051b93920e18599396129598c7c0b3ef154c 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => 'skapade sidan',
     'page_create_notification'    => 'Sidan har skapats',
@@ -37,13 +35,13 @@ return [
     'book_sort'                   => 'sorterade boken',
     'book_sort_notification'      => 'Boken har sorterats om',
 
-    // Shelves
-    'bookshelf_create'                => 'skapade hyllan',
-    'bookshelf_create_notification'   => 'Hyllan har skapats',
-    'bookshelf_update'                => 'uppdaterade hyllan',
-    'bookshelf_update_notification'   => 'Hyllan har uppdaterats',
-    'bookshelf_delete'                => 'tog bort hyllan',
-    'bookshelf_delete_notification'   => 'Hyllan har tagits bort',
+    // Bookshelves
+    'bookshelf_create'            => 'skapade hyllan',
+    'bookshelf_create_notification'    => 'Hyllan har skapats',
+    'bookshelf_update'                 => 'uppdaterade hyllan',
+    'bookshelf_update_notification'    => 'Hyllan har uppdaterats',
+    'bookshelf_delete'                 => 'tog bort hyllan',
+    'bookshelf_delete_notification'    => 'Hyllan har tagits bort',
 
     // Other
     'commented_on'                => 'kommenterade',
index 30e1a1937744fd5678b3918b37e5eb8538612108..96feee117011cab43499e45872f58f0fb3d700ae 100644 (file)
@@ -1,21 +1,15 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => 'Uppgifterna stämmer inte överrens med våra register.',
     'throttle' => 'För många inloggningsförsök. Prova igen om :seconds sekunder.',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => 'Skapa konto',
     'log_in' => 'Logga in',
     'log_in_with' => 'Logga in med :socialDriver',
@@ -27,7 +21,7 @@ return [
     'email' => 'E-post',
     'password' => 'Lösenord',
     'password_confirm' => 'Bekräfta lösenord',
-    'password_hint' => 'Måste vara fler än 5 tecken',
+    'password_hint' => 'Måste vara fler än 7 tecken',
     'forgot_password' => 'Glömt lösenord?',
     'remember_me' => 'Kom ihåg mig',
     'ldap_email_hint' => 'Vänligen ange en e-postadress att använda till kontot.',
@@ -45,23 +39,18 @@ return [
     'register_success' => 'Tack för din registrering! Du är nu registerad och inloggad.',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => 'Återställ lösenord',
     'reset_password_send_instructions' => 'Ange din e-postadress nedan så skickar vi ett mail med en länk för att återställa ditt lösenord.',
     'reset_password_send_button' => 'Skicka återställningslänk',
     'reset_password_sent_success' => 'En länk för att återställa lösenordet har skickats till :email.',
     'reset_password_success' => 'Ditt lösenord har återställts.',
-
     'email_reset_subject' => 'Återställ ditt lösenord till :appName',
     'email_reset_text' => 'Du får detta mail eftersom vi fått en begäran om att återställa lösenordet till ditt konto.',
     'email_reset_not_requested' => 'Om du inte begärt att få ditt lösenord återställt behöver du inte göra någonting',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => 'Bekräfta din e-post på :appName',
     'email_confirm_greeting' => 'Tack för att du gått med i :appName!',
     'email_confirm_text' => 'Vänligen bekräfta din e-postadress genom att klicka på knappen nedan:',
@@ -75,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Vänligen klicka på länken i det mail du fick strax efter att du registerade dig.',
     'email_not_confirmed_resend' => 'Om du inte hittar mailet kan du begära en ny bekräftelse genom att fylla i formuläret nedan.',
     'email_not_confirmed_resend_button' => 'Skicka bekräftelse på nytt',
-];
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
+];
\ No newline at end of file
index 7f0d425a444d494994bd0a6ab3ee62cc73e07c21..4133ee642b4fc9e74dce8a46835acf1e227e29f1 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => 'Avbryt',
     'confirm' => 'Bekräfta',
     'back' => 'Bakåt',
@@ -13,18 +14,14 @@ return [
     'toggle_all' => 'Ändra alla',
     'more' => 'Mer',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => 'Namn',
     'description' => 'Beskrivning',
     'role' => 'Roll',
     'cover_image' => 'Omslagsbild',
     'cover_image_description' => 'Bilden bör vara cirka 440x250px stor.',
-
-    /**
-     * Actions
-     */
+    
+    // Actions
     'actions' => 'Åtgärder',
     'view' => 'Visa',
     'view_all' => 'Visa alla',
@@ -42,16 +39,16 @@ return [
     'remove' => 'Radera',
     'add' => 'Lägg till',
 
-    /**
-     * Sort Options
-     */
+    // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
     'sort_name' => 'Namn',
     'sort_created_at' => 'Skapad',
     'sort_updated_at' => 'Uppdaterad',
 
-    /**
-     * Misc
-     */
+    // Misc
     'deleted_user' => 'Borttagen användare',
     'no_activity' => 'Ingen aktivitet att visa',
     'no_items' => 'Inga tillgängliga föremål',
@@ -62,22 +59,18 @@ return [
     'grid_view' => 'Rutnätsvy',
     'list_view' => 'Listvy',
     'default' => 'Förvald',
+    'breadcrumb' => 'Breadcrumb',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => 'Visa profil',
     'edit_profile' => 'Redigera profil',
 
-    /**
-     * Layout tabs
-     */
+    // Layout tabs
     'tab_info' => 'Information',
     'tab_content' => 'Innehåll',
 
-    /**
-     * Email Content
-     */
+    // Email Content
     'email_action_help' => 'Om du har problem, klicka på knappen ":actionText", och kopiera och klistra in den här adressen i din webbläsare:',
     'email_rights' => 'Alla rättigheter är reserverade',
 ];
index 8b1e95ec6b31006b1f094742ebc5c30f3cdc724c..5e4085dec792eac2d657d2f016418e76eefa3892 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => 'Val av bild',
     'image_all' => 'Alla',
     'image_all_title' => 'Visa alla bilder',
@@ -24,9 +25,7 @@ return [
     'image_delete_success' => 'Bilden har tagits bort',
     'image_upload_remove' => 'Radera',
 
-    /**
-     * Code editor
-     */
+    // Code Editor
     'code_editor' => 'Redigera kod',
     'code_language' => 'Språk',
     'code_content' => 'Kod',
index 806e88e3d05e38211ad5619b28f4c46c5d8f52e9..bc4b3a4afa55ca808bb9e546c531f4ff241f974a 100644 (file)
@@ -1,9 +1,11 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => 'Nyligen skapat',
     'recently_created_pages' => 'Sidor som skapats nyligen',
     'recently_updated_pages' => 'Sidor som uppdaterats nyligen',
@@ -32,17 +34,13 @@ return [
     'export_pdf' => 'PDF-fil',
     'export_text' => 'Textfil',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => 'Rättigheter',
     'permissions_intro' => 'Dessa rättigheter kommer att överskrida eventuella rollbaserade rättigheter.',
     'permissions_enable' => 'Aktivera anpassade rättigheter',
     'permissions_save' => 'Spara rättigheter',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => 'Sökresultat',
     'search_total_results_found' => ':count resultat|:count resultat',
     'search_clear' => 'Rensa sökning',
@@ -67,9 +65,39 @@ return [
     'search_set_date' => 'Ange datum',
     'search_update' => 'Uppdatera sökning',
 
-    /**
-     * Books
-     */
+    // Shelves
+    'shelf' => 'Hylla',
+    'shelves' => 'Hyllor',
+    'x_shelves' => ':count hylla|:count hyllor',
+    'shelves_long' => 'Bokhyllor',
+    'shelves_empty' => 'Du har inte skapat någon hylla',
+    'shelves_create' => 'Skapa ny hylla',
+    'shelves_popular' => 'Populära hyllor',
+    'shelves_new' => 'Nya hyllor',
+    'shelves_new_action' => 'Ny hylla',
+    'shelves_popular_empty' => 'De populäraste hyllorna kommer hamna här',
+    'shelves_new_empty' => 'De senast skapade hyllorna kommer hamna här',
+    'shelves_save' => 'Spara hylla',
+    'shelves_books' => 'Böcker i denna hylla',
+    'shelves_add_books' => 'Lägg till böcker till hyllan',
+    'shelves_drag_books' => 'Dra böcker hit för att lägga dem på hyllan',
+    'shelves_empty_contents' => 'Denna hylla har inga böcker än',
+    'shelves_edit_and_assign' => 'Redigera hyllan för att lägga till böcker',
+    'shelves_edit_named' => 'Ändra hyllan :name',
+    'shelves_edit' => 'Ändra bokhylla',
+    'shelves_delete' => 'Radera bokhylla',
+    'shelves_delete_named' => 'Radera bokhyllan :name',
+    'shelves_delete_explain' => "Detta kommer radera bokhyllan ':name'. Böckerna på hyllan kommer finnas kvar.",
+    'shelves_delete_confirmation' => 'Är du säker på att du vill radera hyllan?',
+    'shelves_permissions' => 'Bokhyllerättigheter',
+    'shelves_permissions_updated' => 'Bokhyllerättigheterna har ändrats',
+    'shelves_permissions_active' => 'Bokhyllerättigheterna är aktiva',
+    'shelves_copy_permissions_to_books' => 'Kopiera rättigheter till böcker',
+    'shelves_copy_permissions' => 'Kopiera rättigheter',
+    'shelves_copy_permissions_explain' => 'Detta kommer kopiera hyllans rättigheter till alla böcker på den. Se till att du har sparat alla ändringar innan du går vidare.',
+    'shelves_copy_permission_success' => 'Hyllans rättigheter har kopierats till :count böcker',
+
+    // Books
     'book' => 'Bok',
     'books' => 'Böcker',
     'x_books' => ':count bok|:count böcker',
@@ -108,9 +136,7 @@ return [
     'books_sort_show_other' => 'Visa andra böcker',
     'books_sort_save' => 'Spara ordning',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => 'Kapitel',
     'chapters' => 'Kapitel',
     'x_chapters' => ':count kapitel|:count kapitel',
@@ -133,9 +159,7 @@ return [
     'chapters_permissions_success' => 'Rättigheterna för kapitlet har uppdaterats',
     'chapters_search_this' => 'Sök i detta kapitel',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => 'Sida',
     'pages' => 'Sidor',
     'x_pages' => ':count sida|:count sidor',
@@ -152,7 +176,7 @@ return [
     'pages_delete_confirm' => 'Är du säker på att du vill ta bort den här sidan?',
     'pages_delete_draft_confirm' => 'Är du säker på att du vill ta bort det här utkastet?',
     'pages_editing_named' => 'Redigerar sida :pageName',
-    'pages_edit_toggle_header' => 'Växla sidhuvud',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'Spara utkast',
     'pages_edit_draft' => 'Redigera utkast',
     'pages_editing_draft' => 'Redigerar utkast',
@@ -210,53 +234,20 @@ return [
     ],
     'pages_draft_discarded' => 'Utkastet har tagits bort. Redigeringsverktyget har uppdaterats med aktuellt innehåll.',
     'pages_specific' => 'Specifik sida',
+    'pages_is_template' => 'Page Template',
 
-    /**
-     * Shelves
-     */
-    'shelf' => 'Hylla',
-    'shelves' => 'Hyllor',
-    'x_shelves' => ':count hylla|:count hyllor',
-    'shelves_long' => 'Bokhyllor',
-    'shelves_empty' => 'Du har inte skapat någon hylla',
-    'shelves_create' => 'Skapa ny hylla',
-    'shelves_popular' => 'Populära hyllor',
-    'shelves_new' => 'Nya hyllor',
-    'shelves_new_action' => 'Ny hylla',
-    'shelves_popular_empty' => 'De populäraste hyllorna kommer hamna här',
-    'shelves_new_empty' => 'De senast skapade hyllorna kommer hamna här',
-    'shelves_save' => 'Spara hylla',
-    'shelves_books' => 'Böcker i denna hylla',
-    'shelves_add_books' => 'Lägg till böcker till hyllan',
-    'shelves_drag_books' => 'Dra böcker hit för att lägga dem på hyllan',
-    'shelves_empty_contents' => 'Denna hylla har inga böcker än',
-    'shelves_edit_and_assign' => 'Redigera hyllan för att lägga till böcker',
-    'shelves_edit_named' => 'Ändra hyllan :name',
-    'shelves_edit' => 'Ändra bokhylla',
-    'shelves_delete' => 'Radera bokhylla',
-    'shelves_delete_named' => 'Radera bokhyllan :name',
-    'shelves_delete_explain' => "Detta kommer radera bokhyllan ':name'. Böckerna på hyllan kommer finnas kvar.",
-    'shelves_delete_confirmation' => 'Är du säker på att du vill radera hyllan?',
-    'shelves_permissions' => 'Bokhyllerättigheter',
-    'shelves_permissions_updated' => 'Bokhyllerättigheterna har ändrats',
-    'shelves_permissions_active' => 'Bokhyllerättigheterna är aktiva',
-    'shelves_copy_permissions_to_books' => 'Kopiera rättigheter till böcker',
-    'shelves_copy_permissions' => 'Kopiera rättigheter',
-    'shelves_copy_permissions_explain' => 'Detta kommer kopiera hyllans rättigheter till alla böcker på den. Se till att du har sparat alla ändringar innan du går vidare.',
-    'shelves_copy_permission_success' => 'Hyllans rättigheter har kopierats till :count böcker',
-
-    /**
-     * Editor sidebar
-     */
-    'shelf_tags' => 'Hylltaggar',
+    // Editor Sidebar
     'page_tags' => 'Sidtaggar',
     'chapter_tags' => 'Kapiteltaggar',
     'book_tags' => 'Boktaggar',
+    'shelf_tags' => 'Hylltaggar',
     'tag' => 'Tagg',
     'tags' =>  'Taggar',
+    'tag_name' =>  'Tag Name',
     'tag_value' => 'Taggvärde (Frivilligt)',
     'tags_explain' => "Lägg till taggar för att kategorisera ditt innehåll bättre. \n Du kan tilldela ett värde till en tagg för ännu bättre organisering.",
     'tags_add' => 'Lägg till ännu en tagg',
+    'tags_remove' => 'Remove this tag',
     'attachments' => 'Bilagor',
     'attachments_explain' => 'Ladda upp filer eller bifoga länkar till ditt innehåll. Dessa visas i sidokolumnen.',
     'attachments_explain_instant_save' => 'Ändringar här sparas omgående.',
@@ -282,10 +273,14 @@ return [
     'attachments_file_uploaded' => 'Filen har laddats upp',
     'attachments_file_updated' => 'Filen har uppdaterats',
     'attachments_link_attached' => 'Länken har bifogats till sidan',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => 'Användare i :time',
     'profile_created_content' => 'Skapat innehåll',
     'profile_not_created_pages' => ':userName har inte skapat några sidor',
@@ -293,9 +288,7 @@ return [
     'profile_not_created_books' => ':userName har inte skapat några böcker',
     'profile_not_created_shelves' => ':userName har inte skapat några hyllor',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => 'Kommentar',
     'comments' => 'Kommentarer',
     'comment_add' => 'Lägg till kommentar',
@@ -313,11 +306,9 @@ return [
     'comment_delete_confirm' => 'Är du säker på att du vill ta bort den här kommentaren?',
     'comment_in_reply_to' => 'Som svar på :commentId',
 
-    /**
-     * Revision
-     */
+    // Revision
     'revision_delete_confirm' => 'Är du säker på att du vill radera den här versionen?',
+    'revision_restore_confirm' => 'Är du säker på att du vill använda denna revision? Det nuvarande innehållet kommer att ersättas.',
     'revision_delete_success' => 'Revisionen raderad',
-    'revision_cannot_delete_latest' => 'Det går inte att ta bort den senaste versionen.',
-    'revision_restore_confirm' => 'Är du säker på att du vill använda denna revision? Det nuvarande innehållet kommer att ersättas.'
-];
+    'revision_cannot_delete_latest' => 'Det går inte att ta bort den senaste versionen.'
+];
\ No newline at end of file
index 8bc940df04866b191719c01e5b2cf2705fde90b2..64dbf4c1615100e780801bf278dc6c44b98c1b05 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => 'Du har inte tillgång till den här sidan.',
     'permissionJson' => 'Du har inte rätt att utföra den här åtgärden.',
@@ -29,6 +27,7 @@ return [
     'social_account_register_instructions' => 'Om du inte har något konto ännu kan du registerar dig genom att välja :socialAccount.',
     'social_driver_not_found' => 'Drivrutinen för den här tjänsten hittades inte',
     'social_driver_not_configured' => 'Dina inställningar för :socialAccount är inte korrekta.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => 'Kunde inte ladda upp till sökvägen :filePath. Kontrollera att webbservern har skrivåtkomst.',
@@ -82,4 +81,5 @@ return [
     'error_occurred' => 'Ett fel inträffade',
     'app_down' => ':appName är nere just nu',
     'back_soon' => 'Vi är snart tillbaka.',
+
 ];
index aa12db17cf79a6261e0088b7fac5fc47c3c68f0e..c64ca85dc7e7fea9c1e06a0d53a4370765ccd46b 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; Föregående',
     'next'     => 'Nästa &raquo;',
 
index 1f33f550b241525ed3cb5f13a17c2d1afb601330..8924edc7a88881b464cfbc9e4c119f28c34051cd 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => 'Lösenord måste vara minst sex tecken långa och anges likadant två gånger.',
     'user' => "Det finns ingen användare med den e-postadressen.",
     'token' => 'Återställningskoden är ogiltig.',
index 6c44c13fbc09aa08c5b654f85177aae432ad18af..c848ac65148840b7ec9fcea22dde6a092c89da3b 100644 (file)
@@ -1,19 +1,17 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
+    // Common Messages
     'settings' => 'Inställningar',
     'settings_save' => 'Spara inställningar',
     'settings_save_success' => 'Inställningarna har sparats',
 
-    /**
-     * App settings
-     */
+    // App Settings
     'app_customization' => 'Sidanpassning',
     'app_features_security' => 'Funktioner och säkerhet',
     'app_name' => 'Applikationsnamn',
@@ -31,6 +29,7 @@ return [
     'app_editor_desc' => 'Välj vilket redigeringsverktyg som ska användas av alla användare för att redigera sidor.',
     'app_custom_html' => 'Egen HTML i <head>',
     'app_custom_html_desc' => 'Eventuellt innehåll i det här fältet placeras längst ner i <head>-sektionen på varje sida. Detta är användbart för att skriva över stilmaller eller lägga in spårningskoder.',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'Applikationslogotyp',
     'app_logo_desc' => 'Bilden bör vara minst 43px hög. <br>Större bilder skalas ner.',
     'app_primary_color' => 'Primärfärg',
@@ -42,9 +41,7 @@ return [
     'app_disable_comments_toggle' => 'Inaktivera kommentarer',
     'app_disable_comments_desc' => 'Inaktivera kommentarer på alla sidor i applikationen. Befintliga kommentarer visas inte.',
 
-    /**
-     * Registration settings
-     */
+    // Registration Settings
     'reg_settings' => 'Registreringsinställningar',
     'reg_enable' => 'Tillåt registrering',
     'reg_enable_toggle' => 'Tillåt registrering',
@@ -57,9 +54,7 @@ return [
     'reg_confirm_restrict_domain_desc' => 'Ange en kommaseparerad lista över e-postdomäner till vilka du vill begränsa registrering. Användare kommer att skickas ett mail för att bekräfta deras e-post innan de får logga in. <br> Notera att användare kommer att kunna ändra sin e-postadress efter lyckad registrering.',
     'reg_confirm_restrict_domain_placeholder' => 'Ingen begränsning satt',
 
-    /**
-     * Maintenance settings
-     */
+    // Maintenance settings
     'maint' => 'Underhåll',
     'maint_image_cleanup' => 'Rensa bilder',
     'maint_image_cleanup_desc' => "Söker igenom innehåll i sidor & revisioner för att se vilka bilder och teckningar som är i bruk och vilka som är överflödiga. Se till att ta en komplett backup av databas och bilder innan du kör detta.",
@@ -69,9 +64,7 @@ return [
     'maint_image_cleanup_success' => 'Hittade och raderade :count bilder som potentiellt inte används!',
     'maint_image_cleanup_nothing_found' => 'Hittade inga oanvända bilder, så inget har raderats!',
 
-    /**
-     * Role settings
-     */
+    // Role Settings
     'roles' => 'Roller',
     'role_user_roles' => 'Användarroller',
     'role_create' => 'Skapa ny roll',
@@ -79,7 +72,7 @@ return [
     'role_delete' => 'Ta bort roll',
     'role_delete_confirm' => 'Rollen med namn \':roleName\' kommer att tas bort.',
     'role_delete_users_assigned' => 'Det finns :userCount användare som tillhör den här rollen. Om du vill migrera användarna från den här rollen, välj en ny roll nedan.',
-    'role_delete_no_migration' => 'Migrera inte användare',
+    'role_delete_no_migration' => "Migrera inte användare",
     'role_delete_sure' => 'Är du säker på att du vill ta bort den här rollen?',
     'role_delete_success' => 'Rollen har tagits bort',
     'role_edit' => 'Redigera roll',
@@ -92,6 +85,7 @@ return [
     'role_manage_roles' => 'Hantera roller & rättigheter',
     'role_manage_entity_permissions' => 'Hantera rättigheter för alla böcker, kapitel och sidor',
     'role_manage_own_entity_permissions' => 'Hantera rättigheter för egna böcker, kapitel och sidor',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'Hantera appinställningar',
     'role_asset' => 'Tillgång till innehåll',
     'role_asset_desc' => 'Det här är standardinställningarna för allt innehåll i systemet. Eventuella anpassade rättigheter på böcker, kapitel och sidor skriver över dessa inställningar.',
@@ -104,9 +98,7 @@ return [
     'role_users' => 'Användare med denna roll',
     'role_users_none' => 'Inga användare tillhör den här rollen',
 
-    /**
-     * Users
-     */
+    // Users
     'users' => 'Användare',
     'user_profile' => 'Användarprofil',
     'users_add_new' => 'Lägg till användare',
@@ -118,6 +110,8 @@ return [
     'users_role_desc' => 'Välj vilka roller den här användaren ska tilldelas. Om en användare har tilldelats flera roller kommer behörigheterna från dessa roller att staplas och de kommer att få alla rättigheter i de tilldelade rollerna.',
     'users_password' => 'Användarlösenord',
     'users_password_desc' => 'Ange ett lösenord som ska användas för att logga in på sidan. Lösenordet måste vara minst 5 tecken långt.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => 'Externt ID för autentisering',
     'users_external_auth_id_desc' => 'Detta är det ID som används för att matcha användaren när den kommunicerar med ditt LDAP-system.',
     'users_password_warning' => 'Fyll i nedanstående fält endast om du vill byta lösenord:',
@@ -139,5 +133,34 @@ return [
     'users_social_connect' => 'Anslut konto',
     'users_social_disconnect' => 'Koppla från konto',
     'users_social_connected' => ':socialAccount har kopplats till ditt konto.',
-    'users_social_disconnected' => ':socialAccount har kopplats bort från ditt konto.'
+    'users_social_disconnected' => ':socialAccount har kopplats bort från ditt konto.',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index 368a46c0e3a23030fc61f0d66a08fdf9495455b5..490d1d85b2c7ead48198f9dfcb605772526aa2c2 100644 (file)
@@ -1,17 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
+    // Standard laravel validation lines
     'accepted'             => ':attribute måste godkännas.',
     'active_url'           => ':attribute är inte en giltig URL.',
     'after'                => ':attribute måste vara efter :date.',
@@ -34,13 +30,41 @@ return [
     'digits'               => ':attribute måste vara :digits siffror.',
     'digits_between'       => ':attribute måste vara mellan :min och :max siffror.',
     'email'                => ':attribute måste vara en giltig e-postadress.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => ':attribute är obligatoriskt.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => 'Valt värde för :attribute är ogiltigt.',
     'image'                => ':attribute måste vara en bild.',
     'image_extension'      => ':attribute måste ha ett giltigt filtillägg.',
     'in'                   => 'Vald :attribute är ogiltigt.',
     'integer'              => ':attribute måste vara en integer.',
     'ip'                   => ':attribute måste vara en giltig IP-adress.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => ':attribute får inte vara större än :max.',
         'file'    => ':attribute får inte vara större än :max kilobyte.',
@@ -56,6 +80,7 @@ return [
     ],
     'no_double_extension'  => ':attribute får bara ha ett filtillägg.',
     'not_in'               => 'Vald :attribute är inte giltig',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attribute måste vara ett nummer.',
     'regex'                => ':attribute har ett ogiltigt format.',
     'required'             => ':attribute är obligatoriskt.',
@@ -77,32 +102,13 @@ return [
     'url'                  => 'Formatet på :attribute är ogiltigt.',
     'uploaded'             => 'Filen kunde inte laddas upp. Servern kanske inte tillåter filer med denna storlek.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => 'Lösenordet måste bekräftas',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
+    // Custom validation attributes
     'attributes' => [],
-
 ];
diff --git a/resources/lang/tr/activities.php b/resources/lang/tr/activities.php
new file mode 100644 (file)
index 0000000..ccc55e1
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
+return [
+
+    // Pages
+    'page_create'                 => 'saya oluşturuldu',
+    'page_create_notification'    => 'Sayfa Başarıyla Oluşturuldu',
+    'page_update'                 => 'sayfa güncellendi',
+    'page_update_notification'    => 'Sayfa Başarıyla Güncellendi',
+    'page_delete'                 => 'sayfa silindi',
+    'page_delete_notification'    => 'Sayfa Başarıyla Silindi',
+    'page_restore'                => 'sayfa kurtarıldı',
+    'page_restore_notification'   => 'Sayfa Başarıyla Kurtarıldı',
+    'page_move'                   => 'sayfa taşındı',
+
+    // Chapters
+    'chapter_create'              => 'bölüm oluşturuldu',
+    'chapter_create_notification' => 'Bölüm Başarıyla Oluşturuldu',
+    'chapter_update'              => 'bölüm güncellendi',
+    'chapter_update_notification' => 'Bölüm Başarıyla Güncellendi',
+    'chapter_delete'              => 'bölüm silindi',
+    'chapter_delete_notification' => 'Bölüm Başarıyla Silindi',
+    'chapter_move'                => 'bölüm taşındı',
+
+    // Books
+    'book_create'                 => 'kitap oluşturuldu',
+    'book_create_notification'    => 'Kitap Başarıyla Oluşturuldu',
+    'book_update'                 => 'kitap güncellendi',
+    'book_update_notification'    => 'Kitap Başarıyla Güncellendi',
+    'book_delete'                 => 'kitap silindi',
+    'book_delete_notification'    => 'Kitap Başarıyla Silindi',
+    'book_sort'                   => 'kitap düzenlendi',
+    'book_sort_notification'      => 'Kitap Başarıyla Yeniden Sıralandı',
+
+    // Bookshelves
+    'bookshelf_create'            => 'kitaplık oluşturuldu',
+    'bookshelf_create_notification'    => 'Kitaplık Başarıyla Oluşturuldu',
+    'bookshelf_update'                 => 'kitaplık güncellendi',
+    'bookshelf_update_notification'    => 'Kitaplık Başarıyla Güncellendi',
+    'bookshelf_delete'                 => 'kitaplık silindi',
+    'bookshelf_delete_notification'    => 'Kitaplık Başarıyla Silindi',
+
+    // Other
+    'commented_on'                => 'yorum yaptı',
+];
diff --git a/resources/lang/tr/auth.php b/resources/lang/tr/auth.php
new file mode 100644 (file)
index 0000000..7019c51
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
+return [
+
+    'failed' => 'Girilen bilgiler bizdeki kayıtlarla uyuşmuyor.',
+    'throttle' => 'Çok fazla giriş yapmaya çalıştınız. Lütfen :seconds saniye içinde tekrar deneyin.',
+
+    // Login & Register
+    'sign_up' => 'Kayıt Ol',
+    'log_in' => 'Giriş Yap',
+    'log_in_with' => ':socialDriver ile giriş yap',
+    'sign_up_with' => ':socialDriver ile kayıt ol',
+    'logout' => 'Çıkış Yap',
+
+    'name' => 'İsim',
+    'username' => 'Kullanıcı Adı',
+    'email' => 'Email',
+    'password' => 'Şifre',
+    'password_confirm' => 'Şifreyi onayla',
+    'password_hint' => 'En az 5 karakter olmalı',
+    'forgot_password' => 'Şifrenizi mi unuttunuz?',
+    'remember_me' => 'Beni Hatırla',
+    'ldap_email_hint' => 'Hesabı kullanmak istediğiniz e-mail adresinizi giriniz.',
+    'create_account' => 'Hesap Oluştur',
+    'already_have_account' => 'Zaten bir hesabınız var mı?',
+    'dont_have_account' => 'Hesabınız yok mu?',
+    'social_login' => 'Diğer Servisler ile Giriş Yap',
+    'social_registration' => 'Diğer Servisler ile Kayıt Ol',
+    'social_registration_text' => 'Diğer servisler ile kayıt ol ve giriş yap.',
+
+    'register_thanks' => 'Kayıt olduğunuz için teşekkürler!',
+    'register_confirm' => 'Lütfen e-posta adresinizi kontrol edin ve gelen doğrulama bağlantısına tıklayınız. :appName.',
+    'registrations_disabled' => 'Kayıt olma özelliği geçici olarak kısıtlanmıştır',
+    'registration_email_domain_invalid' => 'Bu e-mail sağlayıcısının bu uygulamaya erişim izni yoktur.',
+    'register_success' => 'Artık kayıtlı bir kullanıcı olarak giriş yaptınız.',
+
+
+    // Password Reset
+    'reset_password' => 'Parolayı Sıfırla',
+    'reset_password_send_instructions' => 'Aşağıya e-mail adresinizi girdiğinizde parola yenileme bağlantısı mail adresinize gönderilecektir.',
+    'reset_password_send_button' => '>Sıfırlama Bağlantısını Gönder',
+    'reset_password_sent_success' => 'Sıfırlama bağlantısı :email adresinize gönderildi.',
+    'reset_password_success' => 'Parolanız başarıyla sıfırlandı.',
+    'email_reset_subject' => ':appName şifrenizi sıfırlayın.',
+    'email_reset_text' => ' Parola sıfırlama isteğinde bulunduğunuz için bu maili görüntülüyorsunuz.',
+    'email_reset_not_requested' => 'Eğer bu parola sıfırlama isteğinde bulunmadıysanız herhangi bir işlem yapmanıza gerek yoktur.',
+
+
+    // Email Confirmation
+    'email_confirm_subject' => ':appName için girdiğiniz mail adresiniz onaylayınız',
+    'email_confirm_greeting' => ':appName\'e katıldığınız için teşekkürler!',
+    'email_confirm_text' => 'Lütfen e-mail adresinizi aşağıda bulunan butona tıklayarak onaylayınız:',
+    'email_confirm_action' => 'E-Maili Onayla',
+    'email_confirm_send_error' => 'e-mail onayı gerekli fakat sistem mail göndermeyi başaramadı. Yöneticiniz ile görüşüp kurulumlarda bir sorun olmadığını doğrulayın.',
+    'email_confirm_success' => 'e-mail adresiniz onaylandı!',
+    'email_confirm_resent' => 'Doğrulama maili gönderildi, lütfen gelen kutunuzu kontrol ediniz...',
+
+    'email_not_confirmed' => 'E-mail Adresi Doğrulanmadı',
+    'email_not_confirmed_text' => 'Sağlamış olduğunuz e-mail adresi henüz doğrulanmadı.',
+    'email_not_confirmed_click_link' => 'Lütfen kayıt olduktan kısa süre sonra size gönderilen maildeki bağlantıya tıklayın ve mail adresinizi onaylayın.',
+    'email_not_confirmed_resend' => 'Eğer gelen maili bulamadıysanız aşağıdaki formu tekrar doldurarak onay mailini kendinize tekrar gönderebilirsiniz.',
+    'email_not_confirmed_resend_button' => 'Doğrulama Mailini Yeniden Yolla',
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
+];
\ No newline at end of file
diff --git a/resources/lang/tr/common.php b/resources/lang/tr/common.php
new file mode 100644 (file)
index 0000000..a87a0d5
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
+return [
+
+    // Buttons
+    'cancel' => 'İptal',
+    'confirm' => 'Onayla',
+    'back' => 'Geri',
+    'save' => 'Kaydet',
+    'continue' => 'Devam',
+    'select' => 'Seç',
+    'toggle_all' => 'Hepsini Değiştir',
+    'more' => 'Daha Fazla',
+
+    // Form Labels
+    'name' => 'İsim',
+    'description' => 'Açıklama',
+    'role' => 'Rol',
+    'cover_image' => 'Kapak resmi',
+    'cover_image_description' => 'Bu resim yaklaşık 440x250px boyutlarında olmalıdır.',
+    
+    // Actions
+    'actions' => 'Aksiyonlar',
+    'view' => 'Görüntüle',
+    'view_all' => 'Hepsini Görüntüle',
+    'create' => 'Oluştur',
+    'update' => 'Güncelle',
+    'edit' => 'Düzenle',
+    'sort' => 'Sırala',
+    'move' => 'Taşı',
+    'copy' => 'Kopyala',
+    'reply' => 'Yanıtla',
+    'delete' => 'Sil',
+    'search' => 'Ara',
+    'search_clear' => 'Aramayı Temizle',
+    'reset' => 'Sıfırla',
+    'remove' => 'Kaldır',
+    'add' => 'Ekle',
+
+    // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
+    'sort_name' => 'İsim',
+    'sort_created_at' => 'Oluşturulma Tarihi',
+    'sort_updated_at' => 'Güncellenme Tarihi',
+
+    // Misc
+    'deleted_user' => 'Silinmiş Kullanıcı',
+    'no_activity' => 'Gösterilecek aktivite yok',
+    'no_items' => 'Kullanılabilir öge yok',
+    'back_to_top' => 'Başa dön',
+    'toggle_details' => 'Detayları değiştir',
+    'toggle_thumbnails' => 'Küçük resimleri değiştir',
+    'details' => 'Detaylar',
+    'grid_view' => 'Grid görünümü',
+    'list_view' => 'Liste görünümü',
+    'default' => 'Varsayılan',
+    'breadcrumb' => 'Breadcrumb',
+
+    // Header
+    'profile_menu' => 'Profile Menu',
+    'view_profile' => 'Profili Görüntüle',
+    'edit_profile' => 'Profili Düzenle',
+
+    // Layout tabs
+    'tab_info' => 'Bilgi',
+    'tab_content' => 'İçerik',
+
+    // Email Content
+    'email_action_help' => 'Eğer ":actionText" butonuna tıklamakta zorluk çekiyorsanız, aşağıda bulunan linki kopyalayıp tarayıcınıza yapıştırabilirsiniz.',
+    'email_rights' => 'Bütün hakları saklıdır',
+];
diff --git a/resources/lang/tr/components.php b/resources/lang/tr/components.php
new file mode 100644 (file)
index 0000000..d52f2d0
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
+return [
+
+    // Image Manager
+    'image_select' => 'Görsel Seç',
+    'image_all' => 'Tümü',
+    'image_all_title' => 'Tüm görselleri temizle',
+    'image_book_title' => 'Bu kitaba ait görselleri görüntüle',
+    'image_page_title' => 'Bu sayfaya ait görselleri görüntüle',
+    'image_search_hint' => 'Görsel adı ile ara',
+    'image_uploaded' => ':uploadedDate tarihinde yüklendi',
+    'image_load_more' => 'Daha Fazla ',
+    'image_image_name' => 'Görsel Adı',
+    'image_delete_used' => 'Bu görsel aşağıda bulunan görsellerde kullanılmış.',
+    'image_delete_confirm' => 'Gerçekten bu görseli silmek istiyorsanız sil tuşuna basınız.',
+    'image_select_image' => 'Görsel Seç',
+    'image_dropzone' => 'Görselleri buraya sürükle veya seçmek için buraya tıkla',
+    'images_deleted' => 'Görseller Silindi',
+    'image_preview' => 'Görsel Önizleme',
+    'image_upload_success' => 'Görsel başarıyla yüklendi',
+    'image_update_success' => 'Görsel başarıyla güncellendi',
+    'image_delete_success' => 'Görsel başarıyla silindi',
+    'image_upload_remove' => 'Kaldır',
+
+    // Code Editor
+    'code_editor' => 'Kodu Güncelle',
+    'code_language' => 'Kod Dil',
+    'code_content' => 'Kod İçeriği',
+    'code_save' => 'Kodu Kaydet',
+];
diff --git a/resources/lang/tr/entities.php b/resources/lang/tr/entities.php
new file mode 100644 (file)
index 0000000..f9eb54f
--- /dev/null
@@ -0,0 +1,314 @@
+<?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
+return [
+
+    // Shared
+    'recently_created' => 'Yakın Zamanda Oluşturuldu',
+    'recently_created_pages' => 'Yakın Zamanda Oluşturulmuş Sayfalar',
+    'recently_updated_pages' => 'Yakın Zamanda Güncellenmiş Sayfalar',
+    'recently_created_chapters' => 'Yakın Zamanda Oluşturulmuş Bölümler',
+    'recently_created_books' => 'Yakın Zamanda Olşturulmuş Kitaplar',
+    'recently_created_shelves' => 'Yakın Zamanda Oluşturulmuş Kitaplıklar',
+    'recently_update' => 'Yakın Zamanda Güncellenmiş',
+    'recently_viewed' => 'Yakın Zamanda Görüntülenmiş',
+    'recent_activity' => 'Son Hareketler',
+    'create_now' => 'Hemen bir tane oluştur',
+    'revisions' => 'Revizyonlar',
+    'meta_revision' => 'Revizyon #:revisionCount',
+    'meta_created' => 'Oluşturuldu :timeLength',
+    'meta_created_name' => ':user tarafından :timeLength tarihinde oluşturuldu',
+    'meta_updated' => 'Güncellendi :timeLength',
+    'meta_updated_name' => ':user tarafından :timeLength tarihinde güncellendi',
+    'entity_select' => 'Öğe Seçme',
+    'images' => 'Görseller',
+    'my_recent_drafts' => 'Son Taslaklarım',
+    'my_recently_viewed' => 'Son Görüntülemelerim',
+    'no_pages_viewed' => 'Herhangi bir sayfa görüntülemediniz',
+    'no_pages_recently_created' => 'Yakın zamanda bir sayfa oluşturulmadı',
+    'no_pages_recently_updated' => 'Yakın zamanda bir sayfa güncellenmedi',
+    'export' => 'Dışa Aktar',
+    'export_html' => 'Contained Web Dosyası',
+    'export_pdf' => 'PDF Dosyası',
+    'export_text' => 'Düz Metin Dosyası',
+
+    // Permissions and restrictions
+    'permissions' => 'İzinler',
+    'permissions_intro' => 'Etkinleştirildikten sonra bu izinler diğer diğer bütün izinlerden öncelikli olacaktır.',
+    'permissions_enable' => 'Özelleştirilmiş Yetkileri Etkinleştir',
+    'permissions_save' => 'İzinleri Kaydet',
+
+    // Search
+    'search_results' => 'Arama Sonuçları',
+    'search_total_results_found' => ':count sonuç bulundu |:count toplam sonuç bulundu',
+    'search_clear' => 'Aramaları Temizle',
+    'search_no_pages' => 'Bu aramayla herhangi bir sonuç eşleşmedi',
+    'search_for_term' => ':term için ara',
+    'search_more' => 'Daha Fazla Sonuç',
+    'search_filters' => 'Arama Filtreleri',
+    'search_content_type' => 'İçerik Türü',
+    'search_exact_matches' => 'Tam Eşleşmeler',
+    'search_tags' => 'Etiket Aramaları',
+    'search_options' => 'Ayarlar',
+    'search_viewed_by_me' => 'Benim tarafımdan görüntülendi',
+    'search_not_viewed_by_me' => 'Benim tarafımdan görüntülenmedi',
+    'search_permissions_set' => 'İzinler ayarlandı',
+    'search_created_by_me' => 'Benim tarafımdan oluşturuldu',
+    'search_updated_by_me' => 'Benim tarafımdan güncellendi',
+    'search_date_options' => 'Tarih Seçenekleri',
+    'search_updated_before' => 'Önce güncellendi',
+    'search_updated_after' => 'Sonra güncellendi',
+    'search_created_before' => 'Önce oluşturuldu',
+    'search_created_after' => 'Sonra oluşturuldu',
+    'search_set_date' => 'Tarih Ayarla',
+    'search_update' => 'Aramayı Güncelle',
+
+    // Shelves
+    'shelf' => 'Kitaplık',
+    'shelves' => 'Kitaplıklar',
+    'x_shelves' => ':count Kitaplık|:count Kitaplıklar',
+    'shelves_long' => 'Kitaplıklar',
+    'shelves_empty' => 'Hiç kitaplık oluşturulmadı',
+    'shelves_create' => 'Yeni Kitaplık Oluştur',
+    'shelves_popular' => 'Popüler Kitaplıklar',
+    'shelves_new' => 'Yeni Kitaplıklar',
+    'shelves_new_action' => 'Yeni Kitaplık',
+    'shelves_popular_empty' => 'En popüler kitaplıklar burada görüntülenecek.',
+    'shelves_new_empty' => 'En son oluşturulmuş kitaplıklar burada görüntülenecek.',
+    'shelves_save' => 'Kitaplığı Kaydet',
+    'shelves_books' => 'Bu kitaplıktaki kitaplar',
+    'shelves_add_books' => 'Bu kitaplığa kitap ekle',
+    'shelves_drag_books' => 'Bu kitaplığa kitap eklemek için kitapları buraya sürükle',
+    'shelves_empty_contents' => 'Bu kitaplığa henüz hiç bir kitap atanmamış',
+    'shelves_edit_and_assign' => 'Kitaplığa kitap eklemek için güncelle',
+    'shelves_edit_named' => ':name Kitaplığını Güncelle',
+    'shelves_edit' => 'Kitaplığı Güncelle',
+    'shelves_delete' => 'Kitaplığı Sil',
+    'shelves_delete_named' => ':name Kitaplığını Sil',
+    'shelves_delete_explain' => "Bu işlem :name kitaplığını silecektir. İçerdiği kitaplar silinmeyecektir.",
+    'shelves_delete_confirmation' => 'Bu kitaplığı silmek istediğinizden emin misiniz?',
+    'shelves_permissions' => 'Kitaplık İzinleri',
+    'shelves_permissions_updated' => 'Kitaplık İzinleri Güncellendi',
+    'shelves_permissions_active' => 'Kitaplık İzinleri Aktif',
+    'shelves_copy_permissions_to_books' => 'İzinleri Kitaplara Kopyala',
+    'shelves_copy_permissions' => 'İzinleri Kopyala',
+    'shelves_copy_permissions_explain' => 'Bu işlem sonucunda kitaplığınızın izinleri içerdiği kitaplara da aynen uygulanır. Aktifleştirmeden bu kitaplığa ait izinleri kaydettiğinizden emin olun.',
+    'shelves_copy_permission_success' => 'Kitaplık izinleri :count adet kitaba kopyalandı',
+
+    // Books
+    'book' => 'Kitap',
+    'books' => 'Kitaplar',
+    'x_books' => ':count Kitap|:count Kitaplar',
+    'books_empty' => 'Hiç kitap oluşturulmadı',
+    'books_popular' => 'Popüler Kitaplar',
+    'books_recent' => 'En Son Kitaplar',
+    'books_new' => 'Yeni Kitaplar',
+    'books_new_action' => 'Yeni Kitap',
+    'books_popular_empty' => 'En popüler kitaplar burada görüntülenecek.',
+    'books_new_empty' => 'En yeni kitaplar burada görüntülenecek.',
+    'books_create' => 'Yeni Kitap Oluştur',
+    'books_delete' => 'Kitabı Sil',
+    'books_delete_named' => ':bookName kitabını sil',
+    'books_delete_explain' => 'Bu işlem \':bookName\' kitabını silecek. Bütün sayfa ve bölümler silinecektir.',
+    'books_delete_confirmation' => 'Bu kitabı silmek istediğinizden emin misiniz?',
+    'books_edit' => 'Kitabı Güncelle',
+    'books_edit_named' => ':bookName Kitabını Güncelle',
+    'books_form_book_name' => 'Kitap Adı',
+    'books_save' => 'Kitabı Kaydet',
+    'books_permissions' => 'Kitap İzinleri',
+    'books_permissions_updated' => 'Kitap İzinleri Güncellendi',
+    'books_empty_contents' => 'Bu kitaba ait sayfa veya bölüm oluşturulmadı.',
+    'books_empty_create_page' => 'Yeni sayfa oluştur',
+    'books_empty_sort_current_book' => 'Mevcut kitabı sırala',
+    'books_empty_add_chapter' => 'Yeni bölüm ekle',
+    'books_permissions_active' => 'Kitap İzinleri Aktif',
+    'books_search_this' => 'Bu kitapta ara',
+    'books_navigation' => 'Kitap Navigasyonu',
+    'books_sort' => 'Kitap İçeriklerini Sırala',
+    'books_sort_named' => ':bookName Kitabını Sırala',
+    'books_sort_name' => 'İsme Göre Sırala',
+    'books_sort_created' => 'Oluşturulma Tarihine Göre Sırala',
+    'books_sort_updated' => 'Güncellenme Tarihine Göre Sırala',
+    'books_sort_chapters_first' => 'Önce Bölümler',
+    'books_sort_chapters_last' => 'En Son Bölümler',
+    'books_sort_show_other' => 'Diğer Kitapları Göster',
+    'books_sort_save' => 'Yeni Düzeni Kaydet',
+
+    // Chapters
+    'chapter' => 'Bölüm',
+    'chapters' => 'Bölümler',
+    'x_chapters' => ':count Bölüm|:count Bölümler',
+    'chapters_popular' => 'Popüler Bölümler',
+    'chapters_new' => 'Yeni Bölüm',
+    'chapters_create' => 'Yeni Bölüm Oluştur',
+    'chapters_delete' => 'Bölümü Sil',
+    'chapters_delete_named' => ':chapterName Bölümünü Sil',
+    'chapters_delete_explain' => 'Bu işlem \':chapterName\' kitabını silecek. Bütün sayfalar silinecek ve direkt olarak ana kitab eklenecektir.',
+    'chapters_delete_confirm' => 'Bölümü silmek istediğinizden emin misiniz?',
+    'chapters_edit' => 'Bölümü Güncelle',
+    'chapters_edit_named' => ':chapterName Bölümünü Güncelle',
+    'chapters_save' => 'Bölümü Kaydet',
+    'chapters_move' => 'Bölümü Taşı',
+    'chapters_move_named' => ':chapterName Bölümünü Taşı',
+    'chapter_move_success' => 'Bölüm :bookName Kitabına Taşındı',
+    'chapters_permissions' => 'Bölüm İzinleri',
+    'chapters_empty' => 'Bu bölümde henüz bir sayfa yok.',
+    'chapters_permissions_active' => 'Bölüm İzinleri Aktif',
+    'chapters_permissions_success' => 'Bölüm İzinleri Güncellendi',
+    'chapters_search_this' => 'Bu bölümü ara',
+
+    // Pages
+    'page' => 'Sayfa',
+    'pages' => 'Sayfalar',
+    'x_pages' => ':count Sayfa|:count Sayfalar',
+    'pages_popular' => 'Popüler Sayfalar',
+    'pages_new' => 'Yeni Sayfa',
+    'pages_attachments' => 'Ekler',
+    'pages_navigation' => 'Sayfa Navigasyonu',
+    'pages_delete' => 'Sayfayı Sil',
+    'pages_delete_named' => ':pageName Sayfasını Sil',
+    'pages_delete_draft_named' => ':pageName Taslak Sayfasını Sil',
+    'pages_delete_draft' => 'Taslak Sayfayı Sil',
+    'pages_delete_success' => 'Sayfa silindi',
+    'pages_delete_draft_success' => 'Taslak sayfa silindi',
+    'pages_delete_confirm' => 'Bu sayfayı silmek istediğinizden emin misiniz?',
+    'pages_delete_draft_confirm' => 'Bu taslak sayfayı silmek istediğinizden emin misiniz?',
+    'pages_editing_named' => ':pageName Sayfası Düzenleniyor',
+    'pages_edit_draft_options' => 'Draft Options',
+    'pages_edit_save_draft' => 'Taslağı Kaydet',
+    'pages_edit_draft' => 'Taslak Sayfasını Düzenle',
+    'pages_editing_draft' => 'Taslak Düzenleniyor',
+    'pages_editing_page' => 'Sayfa Düzenleniyor',
+    'pages_edit_draft_save_at' => 'Taslak kaydedildi ',
+    'pages_edit_delete_draft' => 'Taslağı Sl',
+    'pages_edit_discard_draft' => 'Taslağı Yoksay',
+    'pages_edit_set_changelog' => 'Değişiklik Logunu Kaydet',
+    'pages_edit_enter_changelog_desc' => 'Yaptığınız değişiklikler hakkında kısa bir bilgilendirme ekleyin',
+    'pages_edit_enter_changelog' => 'Değişim Günlüğü Ekleyin',
+    'pages_save' => 'Sayfayı Kaydet',
+    'pages_title' => 'Sayfa Başlığı',
+    'pages_name' => 'Sayfa İsmi',
+    'pages_md_editor' => 'Editör',
+    'pages_md_preview' => 'Önizleme',
+    'pages_md_insert_image' => 'Görsel Ekle',
+    'pages_md_insert_link' => 'Öge Linki Ekle',
+    'pages_md_insert_drawing' => 'Çizim Ekle',
+    'pages_not_in_chapter' => 'Sayfa Bu Bölümde Değil',
+    'pages_move' => 'Sayfayı Taşı',
+    'pages_move_success' => 'Sayfa ":parentName"\'a taşındı',
+    'pages_copy' => 'Sayfayı Kopyala',
+    'pages_copy_desination' => 'Kopyalanacak Hedef',
+    'pages_copy_success' => 'Sayfa başarıyla kopyalandı',
+    'pages_permissions' => 'Sayfa İzinleri',
+    'pages_permissions_success' => 'Sayfa izinleri güncellendi',
+    'pages_revision' => 'Revizyon',
+    'pages_revisions' => 'Sayfa Revizyonları',
+    'pages_revisions_named' => ':pageName için Sayfa Revizyonları',
+    'pages_revision_named' => ':pageName için Sayfa Revizyonu',
+    'pages_revisions_created_by' => 'Oluşturan',
+    'pages_revisions_date' => 'Revizyon Tarihi',
+    'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revizyon #:id',
+    'pages_revisions_numbered_changes' => 'Revizyon #:id Değişiklikleri',
+    'pages_revisions_changelog' => 'Değişim Günlüğü',
+    'pages_revisions_changes' => 'Değişiklikler',
+    'pages_revisions_current' => 'Mevcut Versiyon',
+    'pages_revisions_preview' => 'Önizleme',
+    'pages_revisions_restore' => 'Kurtar',
+    'pages_revisions_none' => 'Bu sayfaya ait revizyon yok',
+    'pages_copy_link' => 'Linki kopyala',
+    'pages_edit_content_link' => 'İçeriği Düzenle',
+    'pages_permissions_active' => 'Sayfa İzinleri Aktif',
+    'pages_initial_revision' => 'İlk Yayın',
+    'pages_initial_name' => 'Yeni Sayfa',
+    'pages_editing_draft_notification' => 'Şu anda :timeDiff tarhinde kaydedilmiş olan taslağı düzenlemektesiniz.',
+    'pages_draft_edited_notification' => 'Bu sayfa son girişinizden bu yana güncellendi. Değişiklikleri yoksayıp, kaydetmeden çıkmanız önerilir.',
+    'pages_draft_edit_active' => [
+        'start_a' => ':count kullanıcı bu sayfayı düzenlemeye başladı',
+        'start_b' => ':userName kullanıcısı bu sayfayı düzenlemeye başladı',
+        'time_a' => 'sayfa son güncellendiğinden beri',
+        'time_b' => 'son :minCount dakikada',
+        'message' => ':start :time. Birbirinizin düzenlemelerinin çakışmamasına dikkat edin!',
+    ],
+    'pages_draft_discarded' => 'Taslak yok sayıldı, editör mevcut sayfa içeriği ile güncellendi',
+    'pages_specific' => 'Özel Sayfa',
+    'pages_is_template' => 'Page Template',
+
+    // Editor Sidebar
+    'page_tags' => 'Sayfa Etiketleri',
+    'chapter_tags' => 'Bölüm Etiketleri',
+    'book_tags' => 'Kitap Etiketleri',
+    'shelf_tags' => 'Kitaplık Etiketleri',
+    'tag' => 'Etiket',
+    'tags' =>  'Etiketler',
+    'tag_name' =>  'Tag Name',
+    'tag_value' => 'Etiket İçeriği (Opsiyonel)',
+    'tags_explain' => "İçeriğini daha iyi kategorize etmek için bazı etiketler ekle. Etiketlere değer atayarak daha derin bir organizasyon yapısına sahip olabilirsin.",
+    'tags_add' => 'Başka etiket ekle',
+    'tags_remove' => 'Remove this tag',
+    'attachments' => 'Ekler',
+    'attachments_explain' => 'Sayfanızda göstermek için bazı dosyalar yükleyin veya bazı bağlantılar ekleyin. Bunlar sayfanın sidebarında görülebilir.',
+    'attachments_explain_instant_save' => 'Burada yapılan değişiklikler anında kaydedilir.',
+    'attachments_items' => 'Eklenmiş Ögeler',
+    'attachments_upload' => 'Dosya Yükle',
+    'attachments_link' => 'Link Ekle',
+    'attachments_set_link' => 'Link Düzenle',
+    'attachments_delete_confirm' => 'Eki gerçekten silmek istiyor musunuz?',
+    'attachments_dropzone' => 'Dosyaları buraya sürükle veya  eklemek için buraya tıkla',
+    'attachments_no_files' => 'Hiç bir dosya yüklenmedi',
+    'attachments_explain_link' => 'Eğer dosya yüklememeyi tercih ederseniz link ekleyebilirsiniz. Bu başka bir sayfaya veya buluttaki bir dosyanın linki olabilir.',
+    'attachments_link_name' => 'Bağlantı Adı',
+    'attachment_link' => 'Ek linki',
+    'attachments_link_url' => 'Dosya linki',
+    'attachments_link_url_hint' => 'Dosyanın veya sitenin url adres',
+    'attach' => 'Ekle',
+    'attachments_edit_file' => 'Dosyayı Düzenle',
+    'attachments_edit_file_name' => 'Dosya Adı',
+    'attachments_edit_drop_upload' => 'Dosyaları sürükle veya yüklemek için buraya tıkla',
+    'attachments_order_updated' => 'Ek sırası güncellendi',
+    'attachments_updated_success' => 'Ek detayları güncellendi',
+    'attachments_deleted' => 'Ek silindi',
+    'attachments_file_uploaded' => 'Dosya başarıyla yüklendi',
+    'attachments_file_updated' => 'Dosya başarıyla güncellendi',
+    'attachments_link_attached' => 'Link sayfaya başarıyla eklendi',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
+
+    // Profile View
+    'profile_user_for_x' => 'Kullanıcı :time',
+    'profile_created_content' => 'Oluşturulan İçerik',
+    'profile_not_created_pages' => ':userName herhangi bir sayfa oluşturmadı',
+    'profile_not_created_chapters' => ':userName herhangi bir bölüm oluşturmadı',
+    'profile_not_created_books' => ':userName herhangi bir kitap oluşturmadı',
+    'profile_not_created_shelves' => ':userName herhangi bir kitaplık oluşturmadı',
+
+    // Comments
+    'comment' => 'Yorum',
+    'comments' => 'Yorumlar',
+    'comment_add' => 'Yorum Ekle',
+    'comment_placeholder' => 'Buraya yorum ekle',
+    'comment_count' => '{0} Yorum Yok|{1} 1 Yorum|[2,*] :count Yorun',
+    'comment_save' => 'Yorum Kaydet',
+    'comment_saving' => 'Yorum kaydediliyor...',
+    'comment_deleting' => 'Yorum siliniyor...',
+    'comment_new' => 'Yeni Yorum',
+    'comment_created' => 'yorum yaptı :createDiff',
+    'comment_updated' => ':username tarafından :updateDiff önce güncellendi',
+    'comment_deleted_success' => 'Yorum silindi',
+    'comment_created_success' => 'Yorum eklendi',
+    'comment_updated_success' => 'Yorum güncellendi',
+    'comment_delete_confirm' => 'Bu yorumu silmek istediğinizden emin misiniz?',
+    'comment_in_reply_to' => ':commentId yorumuna yanıt olarak',
+
+    // Revision
+    'revision_delete_confirm' => 'Bu revizyonu silmek istediğinizden emin misiniz?',
+    'revision_restore_confirm' => 'Bu revizyonu yeniden yüklemek istediğinizden emin misiniz? Mevcut sayfa içeriği değiştirilecektir.',
+    'revision_delete_success' => 'Revizyon silindi',
+    'revision_cannot_delete_latest' => 'Son revizyon silinemez.'
+];
\ No newline at end of file
diff --git a/resources/lang/tr/errors.php b/resources/lang/tr/errors.php
new file mode 100644 (file)
index 0000000..8a897b4
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Text shown in error messaging.
+ */
+return [
+
+    // Permissions
+    'permission' => 'Bu sayfaya erişme yetkiniz yok.',
+    'permissionJson' => 'Bu işlemi yapmak için yetkiniz yo.',
+
+    // Auth
+    'error_user_exists_different_creds' => ':email adresi farklı kullanıcı bilgileri ile zaten kullanımda.',
+    'email_already_confirmed' => 'E-mail halihazırda onaylanmış, giriş yapmayı dene.',
+    'email_confirmation_invalid' => 'Bu doğrulama tokenı daha önce kullanılmış veya geçerli değil, lütfen tekrar kayıt olmayı deneyin.',
+    'email_confirmation_expired' => 'Doğrulama token\'ının süresi geçmiş, yeni bir mail gönderildi.',
+    'ldap_fail_anonymous' => 'Anonim LDAP girişi başarısız oldu',
+    'ldap_fail_authed' => 'Verdiğiniz bilgiler ile LDAP girişi başarısız oldu.',
+    'ldap_extension_not_installed' => 'LDAP PHP eklentisi yüklenmedi',
+    'ldap_cannot_connect' => 'LDAP sunucusuna bağlanılamadı, ilk bağlantı başarısız oldu',
+    'social_no_action_defined' => 'Bir aksiyon tanımlanmadı',
+    'social_login_bad_response' => ":socialAccount girişi sırasında hata oluştu: \n:error",
+    'social_account_in_use' => 'Bu :socialAccount zaten kullanımda, :socialAccount hesabıyla giriş yapmayı deneyin.',
+    'social_account_email_in_use' => ':email adresi zaten kullanımda. Eğer zaten bir hesabınız varsa :socialAccount hesabınızı profil ayarları kısmından bağlayabilirsiniz.',
+    'social_account_existing' => 'Bu :socialAccount zaten profilinize eklenmiş.',
+    'social_account_already_used_existing' => 'Bu :socialAccount başka bir kullanıcı tarafından kullanılıyor.',
+    'social_account_not_used' => 'Bu :socialAccount hesabı hiç bir kullanıcıya bağlı değil. Lütfen profil ayarlarına gidiniz ve bağlayınız. ',
+    'social_account_register_instructions' => 'Hala bir hesabınız yoksa :socialAccount ile kayıt olabilirsiniz.',
+    'social_driver_not_found' => 'Social driver bulunamadı',
+    'social_driver_not_configured' => ':socialAccount ayarlarınız doğru bir şekilde ayarlanmadı.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
+
+    // System
+    'path_not_writable' => ':filePath dosya yolu yüklenemedi. Sunucuya yazılabilir olduğundan emin olun.',
+    'cannot_get_image_from_url' => ':url\'den görsel alınamadı',
+    'cannot_create_thumbs' => 'Sunucu küçük resimleri oluşturamadı. Lütfen GD PHP eklentisinin yüklü olduğundan emin olun.',
+    'server_upload_limit' => 'Sunucu bu boyutta dosya yüklemenize izin vermiyor. Lütfen daha küçük boyutta dosya yüklemeyi deneyiniz.',
+    'uploaded'  => 'Sunucu bu boyutta dosya yüklemenize izin vermiyor. Lütfen daha küçük boyutta dosya yüklemeyi deneyiniz.',
+    'image_upload_error' => 'Görsel yüklenirken bir hata oluştu',
+    'image_upload_type_error' => 'Yüklemeye çalıştığınız dosya türü geçerli değildir',
+    'file_upload_timeout' => 'Dosya yüklemesi zaman aşımına uğradı',
+
+    // Attachments
+    'attachment_page_mismatch' => 'Ek güncellemesi sırasında sayfa uyuşmazlığı yaşandı',
+    'attachment_not_found' => 'Ek bulunamadı',
+
+    // Pages
+    'page_draft_autosave_fail' => 'Taslak kaydetme başarısız. Sayfanızı kaydetmeden önce internet bağlantınız olduğundan emin olun',
+    'page_custom_home_deletion' => 'Bu sayfa anasayfa olarak ayarlandığı için silinemez',
+
+    // Entities
+    'entity_not_found' => 'Eleman bulunamadı',
+    'bookshelf_not_found' => 'Kitaplık bulunamadı',
+    'book_not_found' => 'Kitap bulunamadı',
+    'page_not_found' => 'Sayfa bulunamadı',
+    'chapter_not_found' => 'Bölüm bulunamadı',
+    'selected_book_not_found' => 'Seçilen kitap bulunamadı',
+    'selected_book_chapter_not_found' => 'Seçilen kitap veya bölüm bulunamadı',
+    'guests_cannot_save_drafts' => 'Misafirler taslak kaydedemezler',
+
+    // Users
+    'users_cannot_delete_only_admin' => 'Tek olan yöneticiyi silemezsiniz',
+    'users_cannot_delete_guest' => 'Misafir kullanıyıcıyı silemezsiniz',
+
+    // Roles
+    'role_cannot_be_edited' => 'Bu rol düzenlenemez',
+    'role_system_cannot_be_deleted' => 'Bu bir yönetici rolüdür ve silinemez',
+    'role_registration_default_cannot_delete' => 'Bu rol varsayılan yönetici rolü olarak atandığı için silinemez ',
+    'role_cannot_remove_only_admin' => 'Bu kullanıcı yönetici rolü olan tek kullanıcı olduğu için silinemez. Bu kullanıcıyı silmek için önce başka bir kullanıcıya yönetici rolü atayın.',
+
+    // Comments
+    'comment_list' => 'Yorumlar yüklenirken bir hata oluştu.',
+    'cannot_add_comment_to_draft' => 'Taslaklara yorum ekleyemezsiniz.',
+    'comment_add' => 'Yorum eklerken/güncellerken bir hata olıuştu.',
+    'comment_delete' => 'Yorum silinirken bir hata oluştu.',
+    'empty_comment' => 'Boş bir yorum eklenemez.',
+
+    // Error pages
+    '404_page_not_found' => 'Sayfa Bulunamadı',
+    'sorry_page_not_found' => 'Üzgünüz, aradığınız sayfa bulunamıyor.',
+    'return_home' => 'Anasayfaya dön',
+    'error_occurred' => 'Bir Hata Oluştu',
+    'app_down' => ':appName şu anda inaktif',
+    'back_soon' => 'En kısa zamanda aktif hale gelecek.',
+
+];
diff --git a/resources/lang/tr/pagination.php b/resources/lang/tr/pagination.php
new file mode 100644 (file)
index 0000000..8550a18
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
+return [
+
+    'previous' => '&laquo; Önceki',
+    'next'     => 'Sonraki &raquo;',
+
+];
diff --git a/resources/lang/tr/passwords.php b/resources/lang/tr/passwords.php
new file mode 100644 (file)
index 0000000..42e9ef2
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
+return [
+
+    'password' => 'Parolanız en az 6 karakterden oluşmalı ve doğrulama parolası ile eşleşmelidir. ',
+    'user' => "Bu e-mail adresi ile ilişkilendirilmiş bir kullanıcı bulamadık.",
+    'token' => 'Parola yenileme tokeni geçerli değil.',
+    'sent' => 'Parola sıfırlanma bağlantısını e-mail adresinize gönderdik!',
+    'reset' => 'Parolanız sıfırlandı!',
+
+];
diff --git a/resources/lang/tr/settings.php b/resources/lang/tr/settings.php
new file mode 100755 (executable)
index 0000000..bb35fe3
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
+return [
+
+    // Common Messages
+    'settings' => 'Ayarlar',
+    'settings_save' => 'Ayarları Kaydet',
+    'settings_save_success' => 'Ayarlar Kaydedildi',
+
+    // App Settings
+    'app_customization' => 'Özelleştirme',
+    'app_features_security' => 'Özellikler & Güvenlik',
+    'app_name' => 'Uygulama Adı',
+    'app_name_desc' => 'Bu isim başlıkta ve sistem tarafında gönderilen tüm mesajlarda gösterilecektir.',
+    'app_name_header' => 'İsmi başlıkta göster',
+    'app_public_access' => 'Açık Erişim',
+    'app_public_access_desc' => 'Bu özelliği aktif etmek giriş yapmamış misafir kullanıcıların sizin BookStack uygulamanıza erişmesini sağlar',
+    'app_public_access_desc_guest' => 'Kayıtlı olmayan kullanıcılar için erişim yetkisi "Guest" kullanıcısı üzerinden düzenlenebilir.',
+    'app_public_access_toggle' => 'Açık erişime izin ver',
+    'app_public_viewing' => 'Herkese açık görüntülenmeye izin verilsin mi?',
+    'app_secure_images' => 'Daha Yüksek Güvenlikli Görsel Yüklemeleri',
+    'app_secure_images_toggle' => 'Daha yüksek güveblikli görsel yüklemelerine izin ver',
+    'app_secure_images_desc' => 'Performans sebepleri nedeniyle bütün görseller halka açık. Bu opsiyon rastgele ve tahmin edilmesi zor dizileri görsel linklerinin önüne ekler. Dizin indexlerinin kapalı olduğundan emin olun.',
+    'app_editor' => 'Sayfa Editörü',
+    'app_editor_desc' => 'Sayfa düzenlemesi yapılırken hangi editörün kullanılacağını seçin.',
+    'app_custom_html' => 'Özel HTML Head İçeriği',
+    'app_custom_html_desc' => 'Buraya eklenecek olan içerik <head> taginin en sonuna eklenecektir. Bu stilleri override ederken veya analytics eklerken faydalı bir kullanım şeklidir.',
+    'app_custom_html_disabled_notice' => 'Yapılan hatalı değişikliklerin geriye alınabilmesi için bu sayfada özel HTML head içeriği kapalı.',
+    'app_logo' => 'Uygulama Logosu',
+    'app_logo_desc' => 'Bu görsel 43px yüksekliğinde olmalı. <br>Büyük görseller ölçeklenecektir.',
+    'app_primary_color' => 'Uygulamanın Birincil Rengi',
+    'app_primary_color_desc' => 'Bu bir hex değeri olmalıdır. <br>Varsayılan rengi seçmek için boş bırakın.',
+    'app_homepage' => 'Uygulama Anasayfası',
+    'app_homepage_desc' => 'Anasayfada görünmesi için bir view seçin. Sayfa izinleri seçili sayfalar için yok sayılacaktır.',
+    'app_homepage_select' => 'Sayfa seçiniz',
+    'app_disable_comments' => 'Yorumları Engelle',
+    'app_disable_comments_toggle' => 'Yorumları engelle',
+    'app_disable_comments_desc' => 'Yorumları uygulamadaki bütün sayfalar için engelle. <br> Mevcut yorumlar gösterilmeyecektir.',
+
+    // Registration Settings
+    'reg_settings' => 'Kayıt',
+    'reg_enable' => 'Kaydolmaya İzin Ver',
+    'reg_enable_toggle' => 'Kaydolmaya izin ver',
+    'reg_enable_desc' => 'Kayıt olmaya izin verdiğinizde kullanıcılar kendilerini uygulamaya kaydedebilecekler. Kayıt olduktan sonra kendilerine varsayılan kullanıcı rolü atanacaktır.',
+    'reg_default_role' => 'Kayıt olduktan sonra varsayılan kullanıcı rolü',
+    'reg_email_confirmation' => 'Email Doğrulama',
+    'reg_email_confirmation_toggle' => 'E-mail onayı gerektir',
+    'reg_confirm_email_desc' => 'Eğer domain kısıtlaması kullanılıyorsa o zaman email doğrulaması gereklidir ve bu seçenek yok sayılacaktır.',
+    'reg_confirm_restrict_domain' => 'Domain Kısıtlaması',
+    'reg_confirm_restrict_domain_desc' => 'Kısıtlamak istediğiniz email domainlerini vigül ile ayırarak yazınız. Kullanıcılara uygulamaya erişmeden önce adreslerini doğrulamak için bir mail gönderilecektir. <br> Kullanıcılar başarıyla kaydolduktan sonra email adreslerini değiştiremeyeceklerdir.',
+    'reg_confirm_restrict_domain_placeholder' => 'Hiçbir kısıtlama tanımlanmamış',
+
+    // Maintenance settings
+    'maint' => 'Bakım',
+    'maint_image_cleanup' => 'Görsel Temizliği',
+    'maint_image_cleanup_desc' => "Sayfaları ve revizyon içeriklerini tarayarak hangi gösel ve çizimlerin kullanımda olduğunu ve hangilerinin gereksiz olduğunu tespit eder. Bunu başlatmadan veritabanı ve görsellerin tam bir yedeğinin alındığından emin olun.",
+    'maint_image_cleanup_ignore_revisions' => 'Revizyonlardaki görselleri yoksay',
+    'maint_image_cleanup_run' => 'Temizliği Başlat',
+    'maint_image_cleanup_warning' => ':count potansiyel kullanılmayan görsel bulundu. Bu görselleri silmek istediğinizden emin misiniz?',
+    'maint_image_cleanup_success' => ':count potanisyel kullanılmayan görsel bulundu ve silindi!',
+    'maint_image_cleanup_nothing_found' => 'Kullanılmayan görsel bulunamadı ve birşey silinmedi!',
+
+    // Role Settings
+    'roles' => 'Roller',
+    'role_user_roles' => 'Kullanıcı Rolleri',
+    'role_create' => 'Yeni Rol Oluştur',
+    'role_create_success' => 'Rol Başarıyla Oluşturuldu',
+    'role_delete' => 'Rolü Sil',
+    'role_delete_confirm' => 'Bu işlem \':roleName\' rolünü silecektir.',
+    'role_delete_users_assigned' => 'Bu role atanmış :userCount adet kullanıcı var. Eğer bu kullanıcıların rollerini değiştirmek istiyorsanız aşağıdan yeni bir rol seçin.',
+    'role_delete_no_migration' => "Kullanıcıları taşıma",
+    'role_delete_sure' => 'Bu rolü silmek istediğinizden emin misiniz?',
+    'role_delete_success' => 'Rol başarıyla silindi',
+    'role_edit' => 'Rolü Düzenle',
+    'role_details' => 'Rol Detayları',
+    'role_name' => 'Rol Adı',
+    'role_desc' => 'Rolün Kısa Tanımı',
+    'role_external_auth_id' => 'Harici Authentication ID\'leri',
+    'role_system' => 'Sistem Yetkileri',
+    'role_manage_users' => 'Kullanıcıları yönet',
+    'role_manage_roles' => 'Rolleri ve rol izinlerini yönet',
+    'role_manage_entity_permissions' => 'Bütün kitap, bölüm ve sayfa izinlerini yönet',
+    'role_manage_own_entity_permissions' => 'Sahip olunan kitap, bölüm ve sayfaların izinlerini yönet',
+    'role_manage_page_templates' => 'Manage page templates',
+    'role_manage_settings' => 'Uygulama ayarlarını yönet',
+    'role_asset' => 'Asset Yetkileri',
+    'role_asset_desc' => 'Bu izinleri assetlere sistem içinden varsayılan erişimi kontrol eder. Kitaplar, bölümler ve sayfaların izinleri bu izinleri override eder.',
+    'role_asset_admins' => 'Yöneticilere otomatik olarak bütün içeriğe erişim yetkisi verilir fakat bu opsiyonlar UI özelliklerini gösterir veya gizler.',
+    'role_all' => 'Hepsi',
+    'role_own' => 'Sahip Olunan',
+    'role_controlled_by_asset' => 'Yükledikleri asset tarafından kontrol ediliyor',
+    'role_save' => 'Rolü Kaydet',
+    'role_update_success' => 'Rol başarıyla güncellendi',
+    'role_users' => 'Bu roldeki kullanıcılar',
+    'role_users_none' => 'Bu role henüz bir kullanıcı atanmadı',
+
+    // Users
+    'users' => 'Kullanıcılar',
+    'user_profile' => 'Kullanıcı Profili',
+    'users_add_new' => 'Yeni Kullanıcı Ekle',
+    'users_search' => 'Kullanıcıları Ara',
+    'users_details' => 'Kullanıcı Detayları',
+    'users_details_desc' => 'Bu kullanıcı için gösterilecek bir isim ve mail adresi belirleyin. Bu e-mail adresi kullanıcı tarafından giriş yaparken kullanılacak.',
+    'users_details_desc_no_email' => 'Diğer kullanıcılar tarafından tanınabilmesi için bir isim belirleyin.',
+    'users_role' => 'Kullanıcı Rolleri',
+    'users_role_desc' => 'Bu kullanıcının hangi rollere atanabileceğini belirleyin. Eğer bir kullanıcıya birden fazla rol atanırsa, kullanıcı bütün rollerin özelliklerini kullanabilir.',
+    'users_password' => 'Kullanıcı Parolası',
+    'users_password_desc' => 'Kullanıcının giriş yaparken kullanacağı bir parola belirleyin. Parola en az 5 karakter olmalıdır.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
+    'users_external_auth_id' => 'Harici Authentication ID\'si',
+    'users_external_auth_id_desc' => 'Bu ID kullanıcı LDAP sunucu ile bağlantı kurarken kullanılır.',
+    'users_password_warning' => 'Sadece parolanızı değiştirmek istiyorsanız aşağıyı doldurunuz.',
+    'users_system_public' => 'Bu kullanıcı sizin uygulamanızı ziyaret eden bütün misafir kullanıcıları temsil eder. Giriş yapmak için kullanılamaz, otomatik olarak atanır.',
+    'users_delete' => 'Kullanıcı Sil',
+    'users_delete_named' => ':userName kullanıcısını sil ',
+    'users_delete_warning' => 'Bu işlem \':userName\' kullanıcısını sistemden tamamen silecektir.',
+    'users_delete_confirm' => 'Bu kullanıcıyı tamamen silmek istediğinize emin misiniz?',
+    'users_delete_success' => 'Kullanıcılar başarıyla silindi.',
+    'users_edit' => 'Kullanıcıyı Güncelle',
+    'users_edit_profile' => 'Profili Düzenle',
+    'users_edit_success' => 'Kullanıcı başarıyla güncellendi',
+    'users_avatar' => 'Kullanıcı Avatarı',
+    'users_avatar_desc' => 'Bu kullanıcıyı temsil eden bir görsel seçin. Yaklaşık 256px kare olmalıdır.',
+    'users_preferred_language' => 'Tercih Edilen Dil',
+    'users_preferred_language_desc' => 'Bu seçenek kullanıcı arayüzünün dilini değiştirecektir. Herhangi bir kullanıcı içeriğini etkilemeyecektir.',
+    'users_social_accounts' => 'Sosyal Hesaplar',
+    'users_social_accounts_info' => 'Burada diğer hesaplarınızı ekleyerek daha hızlı ve kolay giriş sağlayabilirsiniz. Bir hesabın bağlantısını kesmek daha önce edilnilen erişiminizi kaldırmaz. Profil ayarlarınızdan bağlı sosyal hesabınızın erişimini kaldırınız.',
+    'users_social_connect' => 'Hesap Bağla',
+    'users_social_disconnect' => 'Hesabın Bağlantısını Kes',
+    'users_social_connected' => ':socialAccount hesabı profilinize başarıyla bağlandı.',
+    'users_social_disconnected' => ':socialAccount hesabınızın profilinizle ilişiği başarıyla kesildi.',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
+];
diff --git a/resources/lang/tr/validation.php b/resources/lang/tr/validation.php
new file mode 100644 (file)
index 0000000..f0a12bc
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
+return [
+
+    // Standard laravel validation lines
+    'accepted'             => ':attribute kabul edilmelidir.',
+    'active_url'           => ':attribute geçerli bir URL adresi değildir.',
+    'after'                => ':attribute :date tarihinden sonra bir tarih olmalıdır.',
+    'alpha'                => ':attribute sadece harflerden oluşabilir.',
+    'alpha_dash'           => ':attribute sadece harf, rakam ve tirelerden oluşabilir.',
+    'alpha_num'            => ':attribute sadece harf ve rakam oluşabilir.',
+    'array'                => ':attribute array olmalıdır..',
+    'before'               => ':attribute :date tarihinden önce bir tarih olmalıdır.',
+    'between'              => [
+        'numeric' => ':attribute, :min ve :max değerleri arasında olmalıdır.',
+        'file'    => ':attribute, :min ve :max kilobyte boyutları arasında olmalıdır.',
+        'string'  => ':attribute, :min ve :max karakter arasında olmalıdır.',
+        'array'   => ':attribute :min ve :max öge arasında olmalıdır.',
+    ],
+    'boolean'              => ':attribute true veya false olmalıdır.',
+    'confirmed'            => ':attribute doğrulaması eşleşmiyor.',
+    'date'                 => ':attribute geçerli bir tarih değil.',
+    'date_format'          => ':attribute formatı :format\'ına uymuyor.',
+    'different'            => ':attribute be :other birbirinden farklı olmalıdır.',
+    'digits'               => ':attribute :digits basamaklı olmalıdır.',
+    'digits_between'       => ':attribute :min ve :max basamaklı olmalıdır.',
+    'email'                => ':attribute geçerli bir e-mail adresi olmalıdır.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
+    'filled'               => ':attribute gerekli bir alandır.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
+    'exists'               => 'Seçilen :attribute geçerli bir alan değildir.',
+    'image'                => ':attribute bir görsel olmalıdır.',
+    'image_extension'      => ':attribute geçerli ve desteklenen bir görsel uzantısı değildir.',
+    'in'                   => 'Seçilen :attribute geçerli değildir.',
+    'integer'              => ':attribute bir integer değeri olmalıdır.',
+    'ip'                   => ':attribute geçerli bir IP adresi olmalıdır.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
+    'max'                  => [
+        'numeric' => ':attribute, :max değerinden büyük olmamalıdır.',
+        'file'    => ':attribute, :max kilobyte boyutundan büyük olmamalıdır.',
+        'string'  => ':attribute, :max karakter boyutundan büyük olmamalıdır.',
+        'array'   => ':attribute, en fazla :max öge içermelidir.',
+    ],
+    'mimes'                => ':attribute :values dosya tipinde olmalıdır.',
+    'min'                  => [
+        'numeric' => ':attribute, :min değerinden az olmamalıdır.',
+        'file'    => ':attribute, :min kilobyte boyutundan küçük olmamalıdır.',
+        'string'  => ':attribute, :min karakter boyutundan küçük olmamalıdır.',
+        'array'   => ':attribute, en az :min öge içermelidir.',
+    ],
+    'no_double_extension'  => ':attribute sadece tek bir dosya tipinde olmalıdır.',
+    'not_in'               => 'Seçili :attribute geçerli değildir.',
+    'not_regex'            => 'The :attribute format is invalid.',
+    'numeric'              => ':attribute rakam olmalıdır.',
+    'regex'                => ':attribute formatı geçerli değildir.',
+    'required'             => 'The :attribute field is required. :attribute alanı gereklidir.',
+    'required_if'          => ':other alanı :value değerinde ise :attribute alanı gereklidir.',
+    'required_with'        => 'Eğer :values değeri geçerli ise :attribute alanı gereklidir.',
+    'required_with_all'    => 'Eğer :values değeri geçerli ise :attribute alanı gereklidir. ',
+    'required_without'     => 'Eğer :values değeri geçerli değil ise :attribute alanı gereklidir.',
+    'required_without_all' => 'Eğer :values değerlerinden hiçbiri geçerli değil ise :attribute alanı gereklidir.',
+    'same'                 => ':attribute ve :other eşleşmelidir.',
+    'size'                 => [
+        'numeric' => ':attribute, :size boyutunda olmalıdır.',
+        'file'    => ':attribute, :size kilobyte boyutunda olmalıdır.',
+        'string'  => ':attribute, :size karakter uzunluğunda olmalıdır.',
+        'array'   => ':attribute, :size sayıda öge içermelidir.',
+    ],
+    'string'               => ':attribute string olmalıdır.',
+    'timezone'             => ':attribute geçerli bir alan olmalıdır.',
+    'unique'               => ':attribute daha önce alınmış.',
+    'url'                  => ':attribute formatı geçerli değil.',
+    'uploaded'             => 'Dosya yüklemesi başarısız oldu. Server bu boyutta dosyaları kabul etmiyor olabilir.',
+
+    // Custom validation lines
+    'custom' => [
+        'password-confirm' => [
+            'required_with' => 'Parola onayı gereklidir.',
+        ],
+    ],
+
+    // Custom validation attributes
+    'attributes' => [],
+];
index 585f92269b4b5a0888018dca5536da08049ba0f0..2ed0d5560aeff67d8eac1369cb56aa333a907a9a 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
- * Activity text strings. / Текстові рядки активності.
- * Is used for all the text within activity logs & notifications. / Використовується для всього тексту в журналах активності та сповіщеннях.
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
  */
 return [
 
index cd73f92db239428f4361720c61c38a7f80a56bf9..559c7be77359a3905e37c17080e500205147c804 100644 (file)
@@ -1,8 +1,8 @@
 <?php
 /**
  * Authentication Language Lines
- * The following language lines are used during authentication for various / Під час автентифікації використовуються наступні лінії мов для різних повідомлень
- * messages that we need to display to the user. / які нам потрібно показати користувачеві.
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
  */
 return [
 
@@ -21,7 +21,7 @@ return [
     'email' => 'Email',
     'password' => 'Пароль',
     'password_confirm' => 'Підтвердження пароля',
-    'password_hint' => 'Має бути більше 5 символів',
+    'password_hint' => 'Має бути більше 7 символів',
     'forgot_password' => 'Забули пароль?',
     'remember_me' => 'Запам’ятати мене',
     'ldap_email_hint' => 'Введіть email для цього облікового запису.',
@@ -64,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => 'Будь-ласка, натисніть на посилання в електронному листі, яке було надіслано після реєстрації.',
     'email_not_confirmed_resend' => 'Якщо ви не можете знайти електронний лист, ви можете повторно надіслати підтвердження електронною поштою, на формі нижче.',
     'email_not_confirmed_resend_button' => 'Повторне підтвердження електронної пошти',
-];
+
+    // User Invite
+    'user_invite_email_subject' => 'Вас запросили приєднатися до :appName!',
+    'user_invite_email_greeting' => 'Для вас створено обліковий запис на :appName.',
+    'user_invite_email_text' => 'Натисніть кнопку нижче, щоб встановити пароль облікового запису та отримати доступ:',
+    'user_invite_email_action' => 'Встановити пароль облікового запису',
+    'user_invite_page_welcome' => 'Ласкаво просимо до :appName!',
+    'user_invite_page_text' => 'Для завершення процесу створення облікового запису та отримання доступу вам потрібно задати пароль, який буде використовуватися для входу в :appName в майбутньому.',
+    'user_invite_page_confirm_button' => 'Підтвердити пароль',
+    'user_invite_success' => 'Встановлено пароль, тепер у вас є доступ до :appName!'
+];
\ No newline at end of file
index 552f32e114548df5293ee39abdd04bfcc82241f2..651fb6d1ae28041c786f9256969ba73d245a0dfa 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Common elements found throughout many areas of BookStack. / Загальні елементи, використовуються в багатьох областях BookStack.
+ * Common elements found throughout many areas of BookStack.
  */
 return [
 
@@ -38,8 +38,12 @@ return [
     'reset' => 'Скинути',
     'remove' => 'Видалити',
     'add' => 'Додати',
-    
+
     // Sort Options
+    'sort_options' => 'Параметри сортування',
+    'sort_direction_toggle' => 'Перемикач напрямку сортування',
+    'sort_ascending' => 'За зростанням',
+    'sort_descending' => 'За спаданням',
     'sort_name' => 'Ім\'я',
     'sort_created_at' => 'Дата створення',
     'sort_updated_at' => 'Дата оновлення',
@@ -55,11 +59,13 @@ return [
     'grid_view' => 'Вигляд Сіткою',
     'list_view' => 'Вигляд Списком',
     'default' => 'За замовчуванням',
+    'breadcrumb' => 'Breadcrumb',
 
     // Header
+    'profile_menu' => 'Меню профілю',
     'view_profile' => 'Переглянути профіль',
     'edit_profile' => 'Редагувати профіль',
-    
+
     // Layout tabs
     'tab_info' => 'Інфо',
     'tab_content' => 'Вміст',
index 937774ad0e2ad295e19a4f8540613a08b239e303..0cd7e8804d55beda2ba7d7a79d68511ba2d30640 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Text used in custom JavaScript driven components. / Текст використовується в індивідуальних компонентах, керованих JavaScript.
+ * Text used in custom JavaScript driven components.
  */
 return [
 
index a02ce5dcb862e3a5b9781faf21d4cfd9744790f8..9c59fdb39e3a64feefe543b68de3319caa290819 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
- * Text used for 'Entities' (Document Structure Elements) such as / Текст використовується для "об'єктів" (елементів структури документів), таких як
- * Books, Shelves, Chapters & Pages / Книги, Полиці, Розділи та Сторінки
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
  */
 return [
 
@@ -176,7 +176,7 @@ return [
     'pages_delete_confirm' => 'Ви впевнені, що хочете видалити цю сторінку?',
     'pages_delete_draft_confirm' => 'Ви впевнені, що хочете видалити цю чернетку?',
     'pages_editing_named' => 'Редагування сторінки :pageName',
-    'pages_edit_toggle_header' => 'Переключити заголовок',
+    'pages_edit_draft_options' => 'Параметри чернетки',
     'pages_edit_save_draft' => 'Зберегти чернетку',
     'pages_edit_draft' => 'Редагувати чернетку сторінки',
     'pages_editing_draft' => 'Редагування чернетки',
@@ -234,6 +234,7 @@ return [
     ],
     'pages_draft_discarded' => 'Чернетка відхилена, редактор оновлено з поточним вмістом сторінки',
     'pages_specific' => 'Конкретна сторінка',
+    'pages_is_template' => 'Шаблон сторінки',
 
     // Editor Sidebar
     'page_tags' => 'Теги сторінки',
@@ -242,9 +243,11 @@ return [
     'shelf_tags' => 'Теги полиць',
     'tag' => 'Тег',
     'tags' =>  'Теги',
+    'tag_name' =>  'Назва тегу',
     'tag_value' => 'Значення тегу (необов\'язково)',
     'tags_explain' => "Додайте кілька тегів, щоб краще класифікувати ваш вміст. \n Ви можете присвоїти значення тегу для більш глибокої організації.",
     'tags_add' => 'Додати ще один тег',
+    'tags_remove' => 'Видалити цей тег',
     'attachments' => 'Вкладення',
     'attachments_explain' => 'Завантажте файли, або додайте посилання, які відображатимуться на вашій сторінці. Їх буде видно на бічній панелі сторінки.',
     'attachments_explain_instant_save' => 'Зміни тут зберігаються миттєво.',
@@ -270,6 +273,12 @@ return [
     'attachments_file_uploaded' => 'Файл успішно завантажений',
     'attachments_file_updated' => 'Файл успішно оновлено',
     'attachments_link_attached' => 'Посилання успішно додано до сторінки',
+    'templates' => 'Шаблони',
+    'templates_set_as_template' => 'Сторінка це шаблон',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Замінити вміст сторінки',
+    'templates_append_content' => 'Додати до вмісту сторінки',
+    'templates_prepend_content' => 'Prepend to page content',
 
     // Profile View
     'profile_user_for_x' => 'Користувач вже :time',
@@ -302,4 +311,4 @@ return [
     'revision_restore_confirm' => 'Дійсно відновити цю версію? Вміст поточної сторінки буде замінено.',
     'revision_delete_success' => 'Версія видалена',
     'revision_cannot_delete_latest' => 'Неможливо видалити останню версію.'
-];
+];
\ No newline at end of file
index f41a59faf237a13d4726832b55c5edca7e45f9c4..2dacf077e658323c396d74e2ca6cbeb001592117 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Text shown in error messaging. / Текст відображається в повідомленнях про помилку.
+ * Text shown in error messaging.
  */
 return [
 
@@ -27,6 +27,7 @@ return [
     'social_account_register_instructions' => 'Якщо у вас ще немає облікового запису, ви можете зареєструвати обліковий запис за допомогою параметра :socialAccount.',
     'social_driver_not_found' => 'Драйвер для СоціальноїМережі не знайдено',
     'social_driver_not_configured' => 'Ваші соціальні настройки :socialAccount не правильно налаштовані.',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => 'Не вдається завантажити шлях до файлу :filePath. Переконайтеся, що він доступний для запису на сервер.',
index 4f81e6cd17f4bb8898372d5d589b4f1558c3d3ae..364a29e114aa9ba62bbd78dd3f54eedde682922b 100644 (file)
@@ -1,8 +1,8 @@
 <?php
 /**
- * Pagination Language Lines / Лінії мови вирівнювання по сторінках
- * The following language lines are used by the paginator library to build / Наступні мовні лінії використовуються бібліотекою журналіста для створення
- * the simple pagination links. / простих посилань на сторінки.
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
  */
 return [
 
index c9c451b4c217b56d1c6c2ef0d2aeff56a54ba35c..cae38db5e22b3e7e402684768c77126ad553c530 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Password Reminder Language Lines / Нагадування про пароль
+ * Password Reminder Language Lines
  * The following language lines are the default lines which match reasons
  * that are given by the password broker for a password update attempt has failed.
  */
index 801bf8074179c4f32c1a20a0460834498f738ec4..37428ec99795d253d125b84b2df38d8b39ed3d69 100644 (file)
@@ -1,8 +1,8 @@
 <?php
 /**
- * Settings text strings / Текст налаштувань
- * Contains all text strings used in the general settings sections of BookStack / Містить всі текстові рядки, що використовуються в розділах загальної настройки BookStack
- * including users and roles. / включаючи користувачів та ролі.
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
  */
 return [
 
@@ -29,6 +29,7 @@ return [
     'app_editor_desc' => 'Виберіть, який редактор буде використовуватися всіма користувачами для редагування сторінок.',
     'app_custom_html' => 'Користувацький вміст HTML-заголовку',
     'app_custom_html_desc' => 'Будь-який доданий тут вміст буде вставлено в нижню частину розділу <head> кожної сторінки. Це зручно для перевизначення стилів, або додавання коду аналітики.',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'Логотип програми',
     'app_logo_desc' => 'Це зображення має бути висотою 43px. <br>Великі зображення будуть зменшені.',
     'app_primary_color' => 'Основний колір програми',
@@ -84,6 +85,7 @@ return [
     'role_manage_roles' => 'Керування правами ролей та ролями',
     'role_manage_entity_permissions' => 'Керування всіма правами на книги, розділи та сторінки',
     'role_manage_own_entity_permissions' => 'Керування дозволами на власну книгу, розділ та сторінки',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => 'Керування налаштуваннями програми',
     'role_asset' => 'Дозволи',
     'role_asset_desc' => 'Ці дозволи контролюють стандартні доступи всередині системи. Права на книги, розділи та сторінки перевизначать ці дозволи.',
@@ -108,6 +110,8 @@ return [
     'users_role_desc' => 'Виберіть, до яких ролей буде призначено цього користувача. Якщо користувачеві призначено декілька ролей, дозволи з цих ролей будуть складатись і вони отримуватимуть усі можливості призначених ролей.',
     'users_password' => 'Пароль користувача',
     'users_password_desc' => 'Встановіть пароль для входу. Він повинен містити принаймні 5 символів.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Надіслати лист із запрошенням користувачу',
     'users_external_auth_id' => 'Зовнішній ID автентифікації',
     'users_external_auth_id_desc' => 'Цей ID використовується для пошуку збігу цього користувача під час зв\'язку з LDAP.',
     'users_password_warning' => 'Тільки якщо ви хочете змінити свій пароль, заповніть поля нижче:',
@@ -131,4 +135,32 @@ return [
     'users_social_connected' => 'Обліковий запис :socialAccount успішно додано до вашого профілю.',
     'users_social_disconnected' => 'Обліковий запис :socialAccount був успішно відключений від вашого профілю.',
 
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index 523126382cee2cdf31eff8f844ef98dbf4964c79..5e8172889bf75a0ddebe16d0e751fc1bcd0c6053 100644 (file)
@@ -1,9 +1,9 @@
 <?php
 /**
- * Validation Lines / Стрічки перевірки
- * The following language lines contain the default error messages used by / Наступні мовні лінії містять повідомлення про помилку за замовчуванням, 
- * the validator class. Some of these rules have multiple versions such / що використовуються класом валідатора. Деякі з цих правил мають кілька версій,
- * as the size rules. Feel free to tweak each of these messages here. / таких як правила розмірів. Ви можете налаштувати кожен з цих повідомлень тут.
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
  */
 return [
 
@@ -30,13 +30,41 @@ return [
     'digits'               => ':attribute повинні бути :digits цифрами.',
     'digits_between'       => ':attribute має бути між :min та :max цифр.',
     'email'                => ':attribute повинна бути дійсною електронною адресою.',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => ':attribute поле обов\'язкове.',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => ':attribute має бути більшим ніж :value кілобайт.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => 'Вибраний :attribute недійсний.',
     'image'                => ':attribute повинен бути зображенням.',
     'image_extension'      => ':attribute повинен мати дійсне та підтримуване розширення зображення.',
     'in'                   => 'Вибраний :attribute недійсний.',
     'integer'              => ':attribute повинен бути цілим числом.',
     'ip'                   => ':attribute повинна бути дійсною IP-адресою.',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => ':attribute не може бути більшим за :max.',
         'file'    => ':attribute не може бути більшим за :max кілобайт.',
@@ -52,6 +80,7 @@ return [
     ],
     'no_double_extension'  => ':attribute повинен мати тільки одне розширення файлу.',
     'not_in'               => 'Вибраний :attribute недійсний.',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attribute повинен бути числом.',
     'regex'                => ':attribute формат недійсний.',
     'required'             => ':attribute поле обов\'язкове.',
index 7198710efce37ada9c4ac7aac0a79ceed4bcda15..676a1dd92db0c0c2556ae858b6a0ada0c236b472 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => '创建了页面',
     'page_create_notification'    => '页面已创建成功',
@@ -36,7 +34,7 @@ return [
     'book_delete_notification'    => '图书已删除成功',
     'book_sort'                   => '排序了图书',
     'book_sort_notification'      => '图书已重新排序成功',
-    
+
     // Bookshelves
     'bookshelf_create'            => '创建了书架',
     'bookshelf_create_notification'    => '书架已成功创建',
index 046f2360b6c85b6b05c021adfaa287af36eaae77..62cd0c2432fcd0d531b72b641a4e80bc5fa96f63 100644 (file)
@@ -1,21 +1,15 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => '用户名或密码错误。',
     'throttle' => '您的登录次数过多,请在:seconds秒后重试。',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => '注册',
     'log_in' => '登录',
     'log_in_with' => '以:socialDriver登录',
@@ -27,11 +21,13 @@ return [
     'email' => 'Email地址',
     'password' => '密码',
     'password_confirm' => '确认密码',
-    'password_hint' => '必须超过5个字符',
+    'password_hint' => '必须超过7个字符',
     'forgot_password' => '忘记密码?',
     'remember_me' => '记住我',
     'ldap_email_hint' => '请输入用于此帐户的电子邮件。',
     'create_account' => '创建账户',
+    'already_have_account' => 'Already have an account?',
+    'dont_have_account' => 'Don\'t have an account?',
     'social_login' => 'SNS登录',
     'social_registration' => 'SNS注册',
     'social_registration_text' => '其他服务注册/登录.',
@@ -43,23 +39,18 @@ return [
     'register_success' => '感谢您注册:appName,您现在已经登录。',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => '重置密码',
     'reset_password_send_instructions' => '在下面输入您的Email地址,您将收到一封带有密码重置链接的邮件。',
     'reset_password_send_button' => '发送重置链接',
     'reset_password_sent_success' => '密码重置链接已发送到:email。',
     'reset_password_success' => '您的密码已成功重置。',
-
     'email_reset_subject' => '重置您的:appName密码',
     'email_reset_text' => '您收到此电子邮件是因为我们收到了您的帐户的密码重置请求。',
     'email_reset_not_requested' => '如果您没有要求重置密码,则不需要采取进一步的操作。',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => '确认您在:appName的Email地址',
     'email_confirm_greeting' => '感谢您加入:appName!',
     'email_confirm_text' => '请点击下面的按钮确认您的Email地址:',
@@ -73,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => '请检查注册时收到的电子邮件,然后点击确认链接。',
     'email_not_confirmed_resend' => '如果找不到电子邮件,请通过下面的表单重新发送确认Email。',
     'email_not_confirmed_resend_button' => '重新发送确认Email',
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
 ];
\ No newline at end of file
index cf914983e3f4825cfd1564c25578354cc0c6b8a0..04a826e6fbafe84975e40a622873dcac4836b047 100644 (file)
@@ -1,37 +1,36 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => '取消',
     'confirm' => '确认',
     'back' => '返回',
     'save' => '保存',
     'continue' => '继续',
     'select' => '选择',
+    'toggle_all' => 'Toggle All',
     'more' => '更多',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => '名称',
     'description' => '概要',
     'role' => '角色',
     'cover_image' => '封面图片',
     'cover_image_description' => '该图像大小需要为440x250px。',
     
-    /**
-     * Actions
-     */
+    // Actions
     'actions' => '操作',
     'view' => '浏览',
+    'view_all' => 'View All',
     'create' => '创建',
     'update' => '更新',
     'edit' => '编辑',
     'sort' => '排序',
     'move' => '移动',
-   'copy' => '复制',
+    'copy' => '复制',
     'reply' => '回复',
     'delete' => '删除',
     'search' => '搜索',
@@ -40,9 +39,16 @@ return [
     'remove' => '删除',
     'add' => '添加',
 
-    /**
-     * Misc
-     */
+    // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
+    'sort_name' => 'Name',
+    'sort_created_at' => 'Created Date',
+    'sort_updated_at' => 'Updated Date',
+
+    // Misc
     'deleted_user' => '删除用户',
     'no_activity' => '没有活动要显示',
     'no_items' => '没有可用的项目',
@@ -53,16 +59,18 @@ return [
     'grid_view' => '网格视图',
     'list_view' => '列表视图',
     'default' => '默认',
-       
-    /**
-     * Header
-     */
+    'breadcrumb' => 'Breadcrumb',
+
+    // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => '查看资料',
     'edit_profile' => '编辑资料',
 
-    /**
-     * Email Content
-     */
+    // Layout tabs
+    'tab_info' => 'Info',
+    'tab_content' => 'Content',
+
+    // Email Content
     'email_action_help' => '如果您无法点击“:actionText”按钮,请将下面的网址复制到您的浏览器中打开:',
     'email_rights' => 'All rights reserved',
 ];
index e8cad3c302c986de19df545f6948de355cc78d3a..54d0fb085731550136fa218feb6628def6d11e5a 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => '选择图片',
     'image_all' => '全部',
     'image_all_title' => '查看所有图片',
@@ -24,9 +25,7 @@ return [
     'image_delete_success' => '图片删除成功',
     'image_upload_remove' => '去掉',
 
-    /**
-     * Code editor
-     */
+    // Code Editor
     'code_editor' => '编辑代码',
     'code_language' => '编程语言',
     'code_content' => '代码内容',
index 64a205444957639869ef560e05b0139abac7c6ee..04e8e25bc427835f07a2e4fa2550dc6ea3d5318f 100644 (file)
@@ -1,14 +1,17 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => '最近创建',
     'recently_created_pages' => '最近创建的页面',
     'recently_updated_pages' => '最新页面',
     'recently_created_chapters' => '最近创建的章节',
     'recently_created_books' => '最近创建的图书',
+    'recently_created_shelves' => 'Recently Created Shelves',
     'recently_update' => '最近更新',
     'recently_viewed' => '最近查看',
     'recent_activity' => '近期活动',
@@ -31,17 +34,13 @@ return [
     'export_pdf' => 'PDF文件',
     'export_text' => '纯文本文件',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => '权限',
     'permissions_intro' => '本设置优先于每个用户角色本身所具有的权限。',
     'permissions_enable' => '启用自定义权限',
     'permissions_save' => '保存权限',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => '搜索结果',
     'search_total_results_found' => '共找到了:count个结果',
     'search_clear' => '清除搜索',
@@ -66,16 +65,16 @@ return [
     'search_set_date' => '设置日期',
     'search_update' => '只显示更新操作',
 
-    /**
-     * Shelves
-     */
+    // Shelves
     'shelf' => '书架',
     'shelves' => '书架',
+    'x_shelves' => ':count Shelf|:count Shelves',
     'shelves_long' => '书架',
     'shelves_empty' => '当前未创建书架',
     'shelves_create' => '创建新书架',
     'shelves_popular' => '热门书架',
     'shelves_new' => '新书架',
+    'shelves_new_action' => 'New Shelf',
     'shelves_popular_empty' => '最热门的书架',
     'shelves_new_empty' => '最新创建的书架',
     'shelves_save' => '保存书架',
@@ -98,9 +97,7 @@ return [
     'shelves_copy_permissions_explain' => '这会将此书架的当前权限设置应用于其中包含的所有图书。 在激活之前,请确保已保存对此书架权限的任何更改。',
     'shelves_copy_permission_success' => '书架权限复制到图书 :count ',
 
-    /**
-     * Books
-     */
+    // Books
     'book' => '图书',
     'books' => '图书',
     'x_books' => ':count本书',
@@ -108,6 +105,7 @@ return [
     'books_popular' => '热门图书',
     'books_recent' => '最近的书',
     'books_new' => '新书',
+    'books_new_action' => 'New Book',
     'books_popular_empty' => '最受欢迎的图书将出现在这里。',
     'books_new_empty' => '最近创建的图书将出现在这里。',
     'books_create' => '创建图书',
@@ -123,7 +121,6 @@ return [
     'books_permissions_updated' => '图书权限已更新',
     'books_empty_contents' => '本书目前没有页面或章节。',
     'books_empty_create_page' => '创建页面',
-    'books_empty_or' => '或',
     'books_empty_sort_current_book' => '排序当前图书',
     'books_empty_add_chapter' => '添加章节',
     'books_permissions_active' => '有效的图书权限',
@@ -131,12 +128,15 @@ return [
     'books_navigation' => '图书导航',
     'books_sort' => '排序图书内容',
     'books_sort_named' => '排序图书「:bookName」',
+    'books_sort_name' => 'Sort by Name',
+    'books_sort_created' => 'Sort by Created Date',
+    'books_sort_updated' => 'Sort by Updated Date',
+    'books_sort_chapters_first' => 'Chapters First',
+    'books_sort_chapters_last' => 'Chapters Last',
     'books_sort_show_other' => '显示其他图书',
     'books_sort_save' => '保存新顺序',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => '章节',
     'chapters' => '章节',
     'x_chapters' => ':count个章节',
@@ -159,9 +159,7 @@ return [
     'chapters_permissions_success' => '章节权限已更新',
     'chapters_search_this' => '从本章节搜索',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => '页面',
     'pages' => '页面',
     'x_pages' => ':count个页面',
@@ -178,7 +176,7 @@ return [
     'pages_delete_confirm' => '您确定要删除此页面吗?',
     'pages_delete_draft_confirm' => '您确定要删除此草稿页面吗?',
     'pages_editing_named' => '正在编辑页面“:pageName”',
-    'pages_edit_toggle_header' => '显示/隐藏导航栏',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => '保存草稿',
     'pages_edit_draft' => '编辑页面草稿',
     'pages_editing_draft' => '正在编辑草稿',
@@ -196,11 +194,11 @@ return [
     'pages_md_preview' => '预览',
     'pages_md_insert_image' => '插入图片',
     'pages_md_insert_link' => '插入实体链接',
-       'pages_md_insert_drawing' => '插入图表',
+    'pages_md_insert_drawing' => '插入图表',
     'pages_not_in_chapter' => '本页面不在某章节中',
     'pages_move' => '移动页面',
     'pages_move_success' => '页面已移动到「:parentName」',
-       'pages_copy' => '复制页面',
+    'pages_copy' => '复制页面',
     'pages_copy_desination' => '复制目的地',
     'pages_copy_success' => '页面复制完成',
     'pages_permissions' => '页面权限',
@@ -212,6 +210,8 @@ return [
     'pages_revisions_created_by' => '创建者',
     'pages_revisions_date' => '修订日期',
     'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
     'pages_revisions_changelog' => '更新说明',
     'pages_revisions_changes' => '说明',
     'pages_revisions_current' => '当前版本',
@@ -233,20 +233,21 @@ return [
         'message' => ':time,:start。注意不要覆盖对方的更新!',
     ],
     'pages_draft_discarded' => '草稿已丢弃,编辑器已更新到当前页面内容。',
-       'pages_specific' => '具体页面',
+    'pages_specific' => '具体页面',
+    'pages_is_template' => 'Page Template',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => '页面标签',
-       'chapter_tags' => '章节标签',
-       'book_tags' => '图书标签',
+    'chapter_tags' => '章节标签',
+    'book_tags' => '图书标签',
     'shelf_tags' => '书架标签',
     'tag' => '标签',
     'tags' =>  '标签',
+    'tag_name' =>  'Tag Name',
     'tag_value' => '标签值 (Optional)',
     'tags_explain' => "添加一些标签以更好地对您的内容进行分类。\n您可以为标签分配一个值,以进行更深入的组织。",
     'tags_add' => '添加另一个标签',
+    'tags_remove' => 'Remove this tag',
     'attachments' => '附件',
     'attachments_explain' => '上传一些文件或附加一些链接显示在您的网页上。这些在页面的侧边栏中可见。',
     'attachments_explain_instant_save' => '这里的更改将立即保存。Changes here are saved instantly.',
@@ -256,7 +257,7 @@ return [
     'attachments_set_link' => '设置链接',
     'attachments_delete_confirm' => '确认您想要删除此附件后,请点击删除。',
     'attachments_dropzone' => '删除文件或点击此处添加文件',
-    'attachments_no_files' => '尚未上传文件', // No files have been uploaded
+    'attachments_no_files' => '尚未上传文件',
     'attachments_explain_link' => '如果您不想上传文件,则可以附加链接,这可以是指向其他页面的链接,也可以是指向云端文件的链接。',
     'attachments_link_name' => '链接名',
     'attachment_link' => '附件链接',
@@ -272,22 +273,25 @@ return [
     'attachments_file_uploaded' => '附件上传成功',
     'attachments_file_updated' => '附件更新成功',
     'attachments_link_attached' => '链接成功附加到页面',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => '来这里:time了',
     'profile_created_content' => '已创建内容',
     'profile_not_created_pages' => ':userName尚未创建任何页面',
     'profile_not_created_chapters' => ':userName尚未创建任何章节',
     'profile_not_created_books' => ':userName尚未创建任何图书',
+    'profile_not_created_shelves' => ':userName has not created any shelves',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => '评论',
     'comments' => '评论',
-       'comment_add' => '添加评论',
+    'comment_add' => '添加评论',
     'comment_placeholder' => '在这里评论',
     'comment_count' => '{0} 无评论|[1,*] :count条评论',
     'comment_save' => '保存评论',
@@ -302,10 +306,9 @@ return [
     'comment_delete_confirm' => '你确定要删除这条评论?',
     'comment_in_reply_to' => '回复 :commentId',
 
-    /**
-     * Revision
-     */
+    // Revision
     'revision_delete_confirm' => '您确定要删除此修订版吗?',
+    'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
     'revision_delete_success' => '修订删除',
     'revision_cannot_delete_latest' => '无法删除最新版本。'
-];
+];
\ No newline at end of file
index aa1c648d4a96572fb1513e59ea43746d11ca5e1d..f500a58dc6bfba41c6111c1c881d71997220fd61 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => '您无权访问所请求的页面。',
     'permissionJson' => '您无权执行所请求的操作。',
@@ -29,20 +27,21 @@ return [
     'social_account_register_instructions' => '如果您还没有帐户,您可以使用 :socialAccount 选项注册账户。',
     'social_driver_not_found' => '未找到社交驱动程序',
     'social_driver_not_configured' => '您的:socialAccount社交设置不正确。',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => '无法上传到文件路径“:filePath”,请确保它可写入服务器。',
     'cannot_get_image_from_url' => '无法从 :url 中获取图片',
     'cannot_create_thumbs' => '服务器无法创建缩略图,请检查您是否安装了GD PHP扩展。',
     'server_upload_limit' => '服务器不允许上传此大小的文件。 请尝试较小的文件。',
-       'uploaded'  => 'The server does not allow uploads of this size. Please try a smaller file size.',
+    'uploaded'  => 'The server does not allow uploads of this size. Please try a smaller file size.',
     'image_upload_error' => '上传图片时发生错误',
     'image_upload_type_error' => '上传的图像类型无效',
     'file_upload_timeout' => '文件上传已超时。',
 
     // Attachments
     'attachment_page_mismatch' => '附件更新期间的页面不匹配',
-       'attachment_not_found' => '找不到附件',
+    'attachment_not_found' => '找不到附件',
 
     // Pages
     'page_draft_autosave_fail' => '无法保存草稿,确保您在保存页面之前已经连接到互联网',
@@ -50,7 +49,7 @@ return [
 
     // Entities
     'entity_not_found' => '未找到实体',
-       'bookshelf_not_found' => '未找到书架',
+    'bookshelf_not_found' => '未找到书架',
     'book_not_found' => '未找到图书',
     'page_not_found' => '未找到页面',
     'chapter_not_found' => '未找到章节',
@@ -66,6 +65,7 @@ return [
     'role_cannot_be_edited' => '无法编辑该角色',
     'role_system_cannot_be_deleted' => '无法删除系统角色',
     'role_registration_default_cannot_delete' => '无法删除设置为默认注册的角色',
+    'role_cannot_remove_only_admin' => 'This user is the only user assigned to the administrator role. Assign the administrator role to another user before attempting to remove it here.',
 
     // Comments
     'comment_list' => '提取评论时出现错误。',
@@ -81,4 +81,5 @@ return [
     'error_occurred' => '出现错误',
     'app_down' => ':appName现在正在关闭',
     'back_soon' => '请耐心等待网站的恢复。',
+
 ];
index f1fc4b5aee5a9d62ba53d1d7365225ac1247e50a..845ae07a566b894bbfc738a7fbb606a0825a7116 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; 上一页',
     'next'     => '下一页 &raquo;',
 
index d4ba50c490853d12616cb325e89052a75d2b2da1..afdad84238a5df72b4a4d2865a66e28598374ca3 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => '密码必须至少包含六个字符并与确认相符。',
     'user' => "使用该Email地址的用户不存在。",
     'token' => '此密码重置令牌无效。',
index 2a5fcba6cac83d8d7ec5aef0f5dc7e0716a86b34..bc489376c21a67624ddeaf863167e18a1aad42d0 100755 (executable)
@@ -1,59 +1,60 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
-
+    // Common Messages
     'settings' => '设置',
     'settings_save' => '保存设置',
     'settings_save_success' => '设置已保存',
 
-    /**
-     * App settings
-     */
-
-    'app_settings' => 'App设置',
+    // App Settings
+    'app_customization' => 'Customization',
+    'app_features_security' => 'Features & Security',
     'app_name' => 'App名',
     'app_name_desc' => '此名称将在网页头部和Email中显示。',
     'app_name_header' => '在网页头部显示应用名?',
+    'app_public_access' => 'Public Access',
+    'app_public_access_desc' => 'Enabling this option will allow visitors, that are not logged-in, to access content in your BookStack instance.',
+    'app_public_access_desc_guest' => 'Access for public visitors can be controlled through the "Guest" user.',
+    'app_public_access_toggle' => 'Allow public access',
     'app_public_viewing' => '允许公众查看?',
     'app_secure_images' => '启用更高安全性的图片上传?',
+    'app_secure_images_toggle' => 'Enable higher security image uploads',
     'app_secure_images_desc' => '出于性能原因,所有图像都是公开的。这个选项会在图像的网址前添加一个随机的,难以猜测的字符串,从而使直接访问变得困难。',
     'app_editor' => '页面编辑器',
     'app_editor_desc' => '选择所有用户将使用哪个编辑器来编辑页面。',
     'app_custom_html' => '自定义HTML头部内容',
     'app_custom_html_desc' => '此处添加的任何内容都将插入到每个页面的<head>部分的底部,这对于覆盖样式或添加分析代码很方便。',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'App Logo',
     'app_logo_desc' => '这个图片的高度应该为43px。<br>大图片将会被缩小。',
     'app_primary_color' => 'App主色',
     'app_primary_color_desc' => '这应该是一个十六进制值。<br>保留为空以重置为默认颜色。',
     'app_homepage' => 'App主页',
     'app_homepage_desc' => '选择要在主页上显示的页面来替换默认的视图,选定页面的访问权限将被忽略。',
-    'app_homepage_default' => '默认主页视图选择',
+    'app_homepage_select' => 'Select a page',
     'app_disable_comments' => '禁用评论',
+    'app_disable_comments_toggle' => 'Disable comments',
     'app_disable_comments_desc' => '在App的所有页面上禁用评论,现有评论也不会显示出来。',
 
-    /**
-     * Registration settings
-     */
-
+    // Registration Settings
     'reg_settings' => '注册设置',
-    'reg_allow' => '允许注册?',
+    'reg_enable' => 'Enable Registration',
+    'reg_enable_toggle' => 'Enable registration',
+    'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
     'reg_default_role' => '注册后的默认用户角色',
-    'reg_confirm_email' => '需要Email验证?',
+    'reg_email_confirmation' => 'Email Confirmation',
+    'reg_email_confirmation_toggle' => 'Require email confirmation',
     'reg_confirm_email_desc' => '如果使用域名限制,则需要Email验证,并且该值将被忽略。',
     'reg_confirm_restrict_domain' => '域名限制',
     'reg_confirm_restrict_domain_desc' => '输入您想要限制注册的Email域名列表,用逗号隔开。在被允许与应用程序交互之前,用户将被发送一封Email来确认他们的地址。<br>注意用户在注册成功后可以修改他们的Email地址。',
     'reg_confirm_restrict_domain_placeholder' => '尚未设置限制',
 
-    /**
-     * Maintenance settings
-     */
-
+    // Maintenance settings
     'maint' => '维护',
     'maint_image_cleanup' => '清理图像',
     'maint_image_cleanup_desc' => "扫描页面和修订内容以检查哪些图像是正在使用的以及哪些图像是多余的。确保在运行前创建完整的数据库和映像备份。",
@@ -63,10 +64,7 @@ return [
     'maint_image_cleanup_success' => '找到并删除了 :count 张可能未使用的图像!',
     'maint_image_cleanup_nothing_found' => '找不到未使用的图像,没有删除!',
 
-    /**
-     * Role settings
-     */
-
+    // Role Settings
     'roles' => '角色',
     'role_user_roles' => '用户角色',
     'role_create' => '创建角色',
@@ -81,16 +79,17 @@ return [
     'role_details' => '角色详细信息',
     'role_name' => '角色名',
     'role_desc' => '角色简述',
-       'role_external_auth_id' => '外部身份认证ID',
+    'role_external_auth_id' => '外部身份认证ID',
     'role_system' => '系统权限',
     'role_manage_users' => '管理用户',
     'role_manage_roles' => '管理角色与角色权限',
     'role_manage_entity_permissions' => '管理所有图书,章节和页面的权限',
     'role_manage_own_entity_permissions' => '管理自己的图书,章节和页面的权限',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => '管理App设置',
     'role_asset' => '资源许可',
     'role_asset_desc' => '对系统内资源的默认访问许可将由这些权限控制。单独设置在书籍,章节和页面上的权限将覆盖这里的权限设定。',
-       'role_asset_admins' => '管理员可自动获得对所有内容的访问权限,但这些选项可能会显示或隐藏UI选项。',
+    'role_asset_admins' => '管理员可自动获得对所有内容的访问权限,但这些选项可能会显示或隐藏UI选项。',
     'role_all' => '全部的',
     'role_own' => '拥有的',
     'role_controlled_by_asset' => '由其所在的资源来控制',
@@ -99,16 +98,22 @@ return [
     'role_users' => '此角色的用户',
     'role_users_none' => '目前没有用户被分配到这个角色',
 
-    /**
-     * Users
-     */
-
+    // Users
     'users' => '用户',
     'user_profile' => '用户资料',
     'users_add_new' => '添加用户',
     'users_search' => '搜索用户',
+    'users_details' => 'User Details',
+    'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.',
+    'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.',
     'users_role' => '用户角色',
+    'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
+    'users_password' => 'User Password',
+    'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 6 characters long.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => '外部身份认证ID',
+    'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your LDAP system.',
     'users_password_warning' => '如果您想更改密码,请填写以下内容:',
     'users_system_public' => '此用户代表访问您的App的任何访客。它不能用于登录,而是自动分配。',
     'users_delete' => '删除用户',
@@ -122,10 +127,40 @@ return [
     'users_avatar' => '用户头像',
     'users_avatar_desc' => '当前图片应该为约256px的正方形。',
     'users_preferred_language' => '语言',
+    'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
     'users_social_accounts' => '社交账户',
     'users_social_accounts_info' => '在这里,您可以绑定您的其他帐户,以便更快更轻松地登录。如果您选择解除绑定,之后将不能通过此社交账户登录,请设置社交账户来取消本App的访问权限。',
     'users_social_connect' => '绑定账户',
     'users_social_disconnect' => '解除绑定账户',
     'users_social_connected' => ':socialAccount 账户已经成功绑定到您的资料。',
     'users_social_disconnected' => ':socialAccount 账户已经成功解除绑定。',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index ae1ffd619e3228d68a50a554b08063be4201a481..e328f6c38714aa3ae93c9bbd077ff306c9a9b37c 100644 (file)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => ':attribute 需要被同意。',
     'active_url'           => ':attribute 并不是一个有效的网址',
     'after'                => ':attribute 必须是在 :date 后的日期。',
@@ -35,12 +30,41 @@ return [
     'digits'               => ':attribute 必须为:digits位数。',
     'digits_between'       => ':attribute 必须为:min到:max位数。',
     'email'                => ':attribute 必须是有效的电子邮件地址。',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => ':attribute 字段是必需的。',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => '选中的 :attribute 无效。',
     'image'                => ':attribute 必须是一个图片。',
+    'image_extension'      => 'The :attribute must have a valid & supported image extension.',
     'in'                   => '选中的 :attribute 无效。',
     'integer'              => ':attribute 必须是一个整数。',
     'ip'                   => ':attribute 必须是一个有效的IP地址。',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => ':attribute 不能超过:max。',
         'file'    => ':attribute 不能超过:max KB。',
@@ -54,7 +78,9 @@ return [
         'string'  => ':attribute 至少为:min个字符。',
         'array'   => ':attribute 至少有:min项。',
     ],
+    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => '选中的 :attribute 无效。',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attribute 必须是一个数。',
     'regex'                => ':attribute 格式无效。',
     'required'             => ':attribute 字段是必需的。',
@@ -74,35 +100,15 @@ return [
     'timezone'             => ':attribute 必须是有效的区域。',
     'unique'               => ':attribute 已经被使用。',
     'url'                  => ':attribute 格式无效。',
+    'uploaded'             => 'The file could not be uploaded. The server may not accept files of this size.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => '需要确认密码',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
index eb23e35a1fcf3c85140a72e4114217f378947c25..5cf2bd3cf01c6ccd7d1e1994049b3085a7c661e3 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => '建立了頁面',
     'page_create_notification'    => '頁面已建立成功',
index f44ac8af0560dfd217f241be6c5bd5b2350c6727..4e834f70068498acedda9a70e3b5eea253184f71 100644 (file)
@@ -1,21 +1,15 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => '使用者名稱或密碼錯誤。',
     'throttle' => '您的登入次數過多,請在:seconds秒後重試。',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => '註冊',
     'log_in' => '登入',
     'log_in_with' => '以:socialDriver登入',
@@ -27,11 +21,13 @@ return [
     'email' => 'Email位址',
     'password' => '密碼',
     'password_confirm' => '確認密碼',
-    'password_hint' => '必須超過5個字元',
+    'password_hint' => '必須超過7個字元',
     'forgot_password' => '忘記密碼?',
     'remember_me' => '記住我',
     'ldap_email_hint' => '請輸入用於此帳號的電子郵件。',
     'create_account' => '建立帳號',
+    'already_have_account' => 'Already have an account?',
+    'dont_have_account' => 'Don\'t have an account?',
     'social_login' => 'SNS登入',
     'social_registration' => 'SNS註冊',
     'social_registration_text' => '其他服務註冊/登入.',
@@ -43,23 +39,18 @@ return [
     'register_success' => '感謝您註冊:appName,您現在已經登入。',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => '重置密碼',
     'reset_password_send_instructions' => '在下方輸入您的Email位址,您將收到一封帶有密碼重置連結的郵件。',
     'reset_password_send_button' => '發送重置連結',
     'reset_password_sent_success' => '密碼重置連結已發送到:email。',
     'reset_password_success' => '您的密碼已成功重置。',
-
     'email_reset_subject' => '重置您的:appName密碼',
     'email_reset_text' => '您收到此電子郵件是因為我們收到了您的帳號的密碼重置請求。',
     'email_reset_not_requested' => '如果您沒有要求重置密碼,則不需要採取進一步的操作。',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => '確認您在:appName的Email位址',
     'email_confirm_greeting' => '感謝您加入:appName!',
     'email_confirm_text' => '請點選下面的按鈕確認您的Email位址:',
@@ -73,4 +64,14 @@ return [
     'email_not_confirmed_click_link' => '請檢查註冊時收到的電子郵件,然後點選確認連結。',
     'email_not_confirmed_resend' => '如果找不到電子郵件,請透過下面的表單重新發送確認Email。',
     'email_not_confirmed_resend_button' => '重新發送確認Email',
+
+    // User Invite
+    'user_invite_email_subject' => 'You have been invited to join :appName!',
+    'user_invite_email_greeting' => 'An account has been created for you on :appName.',
+    'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
+    'user_invite_email_action' => 'Set Account Password',
+    'user_invite_page_welcome' => 'Welcome to :appName!',
+    'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
+    'user_invite_page_confirm_button' => 'Confirm Password',
+    'user_invite_success' => 'Password set, you now have access to :appName!'
 ];
\ No newline at end of file
index 4eacf4bf06248b4552e4df2e0e00ac2418fec6e6..80147e91a7152dbf0df810cfbf7304f706d4fb4c 100644 (file)
@@ -1,31 +1,30 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => '取消',
     'confirm' => '確認',
     'back' => '返回',
     'save' => '儲存',
     'continue' => '繼續',
     'select' => '選擇',
+    'toggle_all' => 'Toggle All',
     'more' => '更多',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => '名稱',
     'description' => '摘要',
     'role' => '角色',
     'cover_image' => '封面圖片',
     'cover_image_description' => '所使用圖片大小必須是440x250px。',
-
-    /**
-     * Actions
-     */
+    
+    // Actions
     'actions' => '動作',
     'view' => '檢視',
+    'view_all' => 'View All',
     'create' => '建立',
     'update' => '更新',
     'edit' => '編輯',
@@ -40,9 +39,16 @@ return [
     'remove' => '刪除',
     'add' => '新增',
 
-    /**
-     * Misc
-     */
+    // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
+    'sort_name' => 'Name',
+    'sort_created_at' => 'Created Date',
+    'sort_updated_at' => 'Updated Date',
+
+    // Misc
     'deleted_user' => '刪除使用者',
     'no_activity' => '無活動',
     'no_items' => '無項目',
@@ -53,16 +59,18 @@ return [
     'grid_view' => '縮圖檢視',
     'list_view' => '清單撿視',
     'default' => '預設',
+    'breadcrumb' => 'Breadcrumb',
 
-    /**
-     * Header
-     */
+    // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => '檢視資料',
     'edit_profile' => '編輯資料',
 
-    /**
-     * Email Content
-     */
+    // Layout tabs
+    'tab_info' => 'Info',
+    'tab_content' => 'Content',
+
+    // Email Content
     'email_action_help' => '如果您無法點選“:actionText”按鈕,請將下面的網址複製到您的瀏覽器中打開:',
     'email_rights' => 'All rights reserved',
 ];
index 8d90203293716f1ddfb45268ac66a3ee57fc1a7f..bcadecbb6bafb1788ec2f2898afec2ace7a37b89 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => '選擇圖片',
     'image_all' => '全部',
     'image_all_title' => '檢視所有圖片',
@@ -24,9 +25,7 @@ return [
     'image_delete_success' => '圖片刪除成功',
     'image_upload_remove' => '移除',
 
-    /**
-     * Code editor
-     */
+    // Code Editor
     'code_editor' => '編輯程式碼',
     'code_language' => '程式語言',
     'code_content' => '程式碼內容',
index 90299a3335c7f786fc0da53fea9c33c9b145b339..8d49ff2ffe6d917592fa15ee5aaa558e8363b276 100644 (file)
@@ -1,14 +1,17 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => '最近建立',
     'recently_created_pages' => '最近建立的頁面',
     'recently_updated_pages' => '最新頁面',
     'recently_created_chapters' => '最近建立的章節',
     'recently_created_books' => '最近建立的書本',
+    'recently_created_shelves' => 'Recently Created Shelves',
     'recently_update' => '最近更新',
     'recently_viewed' => '最近看過',
     'recent_activity' => '近期活動',
@@ -31,17 +34,13 @@ return [
     'export_pdf' => 'PDF檔案',
     'export_text' => '純文字檔案',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => '權限',
     'permissions_intro' => '本設定優先權高於每個使用者角色本身所具有的權限。',
     'permissions_enable' => '啟用自訂權限',
     'permissions_save' => '儲存權限',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => '搜尋結果',
     'search_total_results_found' => '共找到了:count個結果',
     'search_clear' => '清除搜尋',
@@ -66,16 +65,16 @@ return [
     'search_set_date' => '設定日期',
     'search_update' => '更新搜尋結果',
 
-    /**
-     * Shelves
-     */
+    // Shelves
     'shelf' => '書架',
     'shelves' => '書架',
+    'x_shelves' => ':count Shelf|:count Shelves',
     'shelves_long' => '書架',
     'shelves_empty' => '不存在已建立的書架',
     'shelves_create' => '建立書架',
     'shelves_popular' => '熱門書架',
     'shelves_new' => '新書架',
+    'shelves_new_action' => 'New Shelf',
     'shelves_popular_empty' => '最受歡迎的書架將出現在這裡。',
     'shelves_new_empty' => '最近建立的書架將出現在這裡。',
     'shelves_save' => '儲存書架',
@@ -98,9 +97,7 @@ return [
     'shelves_copy_permissions_explain' => '這會將此書架目前的權限設定套用到所有包含的書本上。在生效之前,請確認您已儲存任何對此書架權限的變更。',
     'shelves_copy_permission_success' => '已將書架的權限複製到:count本書上',
 
-    /**
-     * Books
-     */
+    // Books
     'book' => '書本',
     'books' => '書本',
     'x_books' => ':count本書',
@@ -108,6 +105,7 @@ return [
     'books_popular' => '熱門書本',
     'books_recent' => '最近的書',
     'books_new' => '新書',
+    'books_new_action' => 'New Book',
     'books_popular_empty' => '最受歡迎的書本將出現在這裡。',
     'books_new_empty' => '最近建立的書本將出現在這裡。',
     'books_create' => '建立書本',
@@ -123,7 +121,6 @@ return [
     'books_permissions_updated' => '書本權限已更新',
     'books_empty_contents' => '本書目前沒有頁面或章節。',
     'books_empty_create_page' => '建立頁面',
-    'books_empty_or' => '或',
     'books_empty_sort_current_book' => '排序目前書本',
     'books_empty_add_chapter' => '加入章節',
     'books_permissions_active' => '已啟用此書本的自訂權限',
@@ -131,12 +128,15 @@ return [
     'books_navigation' => '書本導覽',
     'books_sort' => '排序書本內容',
     'books_sort_named' => '排序書本「:bookName」',
+    'books_sort_name' => 'Sort by Name',
+    'books_sort_created' => 'Sort by Created Date',
+    'books_sort_updated' => 'Sort by Updated Date',
+    'books_sort_chapters_first' => 'Chapters First',
+    'books_sort_chapters_last' => 'Chapters Last',
     'books_sort_show_other' => '顯示其他書本',
     'books_sort_save' => '儲存新順序',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => '章節',
     'chapters' => '章節',
     'x_chapters' => ':count個章節',
@@ -159,9 +159,7 @@ return [
     'chapters_permissions_success' => '章節權限已更新',
     'chapters_search_this' => '從本章節搜尋',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => '頁面',
     'pages' => '頁面',
     'x_pages' => ':count個頁面',
@@ -178,7 +176,7 @@ return [
     'pages_delete_confirm' => '您確定要刪除此頁面嗎?',
     'pages_delete_draft_confirm' => '您確定要刪除此草稿頁面嗎?',
     'pages_editing_named' => '正在編輯頁面“:pageName”',
-    'pages_edit_toggle_header' => '顯示/隱藏導覽欄',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => '儲存草稿',
     'pages_edit_draft' => '編輯頁面草稿',
     'pages_editing_draft' => '正在編輯草稿',
@@ -212,6 +210,8 @@ return [
     'pages_revisions_created_by' => '建立者',
     'pages_revisions_date' => '修訂日期',
     'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
     'pages_revisions_changelog' => '更新說明',
     'pages_revisions_changes' => '說明',
     'pages_revisions_current' => '目前版本',
@@ -234,19 +234,20 @@ return [
     ],
     'pages_draft_discarded' => '草稿已丟棄,編輯器已更新到目前頁面內容。',
     'pages_specific' => '指定頁面',
+    'pages_is_template' => 'Page Template',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => '頁面標籤',
     'chapter_tags' => '章節標籤',
     'book_tags' => '書本標籤',
     'shelf_tags' => '書架標籤',
     'tag' => '標籤',
-    'tags' =>  '',
+    'tags' =>  'Tags',
+    'tag_name' =>  'Tag Name',
     'tag_value' => '標籤值 (非必要)',
     'tags_explain' => "加入一些標籤以更好地對您的內容進行分類。\n您可以為標籤分配一個值,以進行更深入的組織。",
     'tags_add' => '加入另一個標籤',
+    'tags_remove' => 'Remove this tag',
     'attachments' => '附件',
     'attachments_explain' => '上傳一些檔案或附加連結顯示在您的網頁上。將顯示在在頁面的側邊欄。',
     'attachments_explain_instant_save' => '這裡的更改將立即儲存。Changes here are saved instantly.',
@@ -256,7 +257,7 @@ return [
     'attachments_set_link' => '設定連結',
     'attachments_delete_confirm' => '確認您想要刪除此附件後,請點選刪除。',
     'attachments_dropzone' => '刪除檔案或點選此處加入檔案',
-    'attachments_no_files' => '尚未上傳檔案', // No files have been uploaded
+    'attachments_no_files' => '尚未上傳檔案',
     'attachments_explain_link' => '如果您不想上傳檔案,則可以附加連結,這可以是指向其他頁面的連結,也可以是指向雲端檔案的連結。',
     'attachments_link_name' => '連結名稱',
     'attachment_link' => '附件連結',
@@ -272,19 +273,22 @@ return [
     'attachments_file_uploaded' => '附件上傳成功',
     'attachments_file_updated' => '附件更新成功',
     'attachments_link_attached' => '連結成功附加到頁面',
+    'templates' => 'Templates',
+    'templates_set_as_template' => 'Page is a template',
+    'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
+    'templates_replace_content' => 'Replace page content',
+    'templates_append_content' => 'Append to page content',
+    'templates_prepend_content' => 'Prepend to page content',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => '來這裡:time了',
     'profile_created_content' => '已建立內容',
     'profile_not_created_pages' => ':userName尚未建立任何頁面',
     'profile_not_created_chapters' => ':userName尚未建立任何章節',
     'profile_not_created_books' => ':userName尚未建立任何書本',
+    'profile_not_created_shelves' => ':userName has not created any shelves',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => '評論',
     'comments' => '評論',
     'comment_add' => '新增評論',
@@ -302,10 +306,9 @@ return [
     'comment_delete_confirm' => '你確定要刪除這條評論?',
     'comment_in_reply_to' => '回覆 :commentId',
 
-    /**
-     * Revision
-     */
+    // Revision
     'revision_delete_confirm' => '您確定要刪除此修訂版嗎?',
+    'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
     'revision_delete_success' => '修訂刪除',
     'revision_cannot_delete_latest' => '無法刪除最新版本。'
-];
+];
\ No newline at end of file
index 2e8050cde4196f08e3e11a03a6d2a8672316a00f..39bdbd6f6bc5dcc0d6c05453d1a0e481813c3212 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => '您沒有權限進入所請求的頁面。',
     'permissionJson' => '您沒有權限執行所請求的操作。',
@@ -29,13 +27,14 @@ return [
     'social_account_register_instructions' => '如果您還沒有帳號,您可以使用 :socialAccount 選項註冊帳號。',
     'social_driver_not_found' => '未找到社交驅動程式',
     'social_driver_not_configured' => '您的:socialAccount社交設定不正確。',
+    'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
 
     // System
     'path_not_writable' => '無法上傳到檔案路徑“:filePath”,請確保它可寫入伺服器。',
     'cannot_get_image_from_url' => '無法從 :url 中獲取圖片',
     'cannot_create_thumbs' => '伺服器無法建立縮圖,請檢查您是否安裝了GD PHP外掛。',
     'server_upload_limit' => '上傳的檔案大小超過伺服器允許上限。請嘗試較小的檔案。',
-    'uploaded' => '上傳的檔案大小超過伺服器允許上限。請嘗試較小的檔案。',
+    'uploaded'  => '上傳的檔案大小超過伺服器允許上限。請嘗試較小的檔案。',
     'image_upload_error' => '上傳圖片時發生錯誤',
     'image_upload_type_error' => '上傳圖片類型錯誤',
     'file_upload_timeout' => '文件上傳已超時。',
@@ -66,6 +65,7 @@ return [
     'role_cannot_be_edited' => '無法編輯這個角色',
     'role_system_cannot_be_deleted' => '無法刪除系統角色',
     'role_registration_default_cannot_delete' => '無法刪除設定為預設註冊的角色',
+    'role_cannot_remove_only_admin' => 'This user is the only user assigned to the administrator role. Assign the administrator role to another user before attempting to remove it here.',
 
     // Comments
     'comment_list' => '讀取評論時發生錯誤。',
@@ -81,4 +81,5 @@ return [
     'error_occurred' => '發生錯誤',
     'app_down' => ':appName現在正在關閉',
     'back_soon' => '請耐心等待網站的恢複。',
+
 ];
index fc2f4a059eaddced8ab7fa8685cda10399cdf01c..6e296de8fa126df9f306d08f5d922bbceda62d86 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; 上一頁',
     'next'     => '下一頁 &raquo;',
 
index aa93eec1ed48fd0ddca6e2db39cc8c9ee7ae4f17..d1f0aeb08646d9c255c04032d65c4bbc99afaa5e 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => '密碼必須至少包含六個字元並與確認相符。',
     'user' => "使用該Email位址的使用者不存在。",
     'token' => '此密碼重置 Session 無效。',
index 335f5a098194571921868d5dfba986493c606dab..0ad899a27e0da558b4e427067d19a3518747d528 100644 (file)
@@ -1,32 +1,35 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles
-     */
-
+    // Common Messages
     'settings' => '設定',
     'settings_save' => '儲存設定',
     'settings_save_success' => '設定已儲存',
 
-    /**
-     * App settings
-     */
-
-    'app_settings' => 'App設定',
+    // App Settings
+    'app_customization' => 'Customization',
+    'app_features_security' => 'Features & Security',
     'app_name' => 'App名',
     'app_name_desc' => '此名稱將在網頁頂端和Email中顯示。',
     'app_name_header' => '在網頁頂端顯示應用名稱?',
+    'app_public_access' => 'Public Access',
+    'app_public_access_desc' => 'Enabling this option will allow visitors, that are not logged-in, to access content in your BookStack instance.',
+    'app_public_access_desc_guest' => 'Access for public visitors can be controlled through the "Guest" user.',
+    'app_public_access_toggle' => 'Allow public access',
     'app_public_viewing' => '開放公開閱覽?',
     'app_secure_images' => '啟用更高安全性的圖片上傳?',
+    'app_secure_images_toggle' => 'Enable higher security image uploads',
     'app_secure_images_desc' => '出於效能考量,所有圖片都是公開的。這個選項會在圖片的網址前加入一個隨機並難以猜測的字元串,從而使直接進入變得困難。',
     'app_editor' => '頁面編輯器',
     'app_editor_desc' => '選擇所有使用者將使用哪個編輯器來編輯頁面。',
     'app_custom_html' => '自訂HTML頂端內容',
     'app_custom_html_desc' => '此處加入的任何內容都將插入到每個頁面的<head>部分的底部,這對於覆蓋樣式或加入分析程式碼很方便。',
+    'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
     'app_logo' => 'App Logo',
     'app_logo_desc' => '這個圖片的高度應該為43px。<br>大圖片將會被縮小。',
     'app_primary_color' => 'App主要配色',
@@ -35,25 +38,23 @@ return [
     'app_homepage_desc' => '選擇要做為首頁的頁面,這將會替換預設首頁,而且這個頁面的權限設定將被忽略。',
     'app_homepage_select' => '預設首頁選擇',
     'app_disable_comments' => '關閉評論',
+    'app_disable_comments_toggle' => 'Disable comments',
     'app_disable_comments_desc' => '在App的所有頁面上關閉評論,已經存在的評論也不會顯示。',
 
-    /**
-     * Registration settings
-     */
-
+    // Registration Settings
     'reg_settings' => '註冊設定',
-    'reg_allow' => '開放註冊?',
+    'reg_enable' => 'Enable Registration',
+    'reg_enable_toggle' => 'Enable registration',
+    'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
     'reg_default_role' => '註冊後的預設使用者角色',
-    'reg_confirm_email' => '需要Email驗證?',
+    'reg_email_confirmation' => 'Email Confirmation',
+    'reg_email_confirmation_toggle' => 'Require email confirmation',
     'reg_confirm_email_desc' => '如果使用網域名稱限制,則需要Email驗證,並且本設定將被忽略。',
     'reg_confirm_restrict_domain' => '網域名稱限制',
     'reg_confirm_restrict_domain_desc' => '輸入您想要限制註冊的Email域域名稱列表,用逗號隔開。在被允許與本系統連結之前,使用者會收到一封Email來確認他們的位址。<br>注意,使用者在註冊成功後可以修改他們的Email位址。',
     'reg_confirm_restrict_domain_placeholder' => '尚未設定限制的網域',
 
-    /**
-     * Maintenance settings
-     */
-
+    // Maintenance settings
     'maint' => '維護',
     'maint_image_cleanup' => '清理圖像',
     'maint_image_cleanup_desc' => "掃描頁面和修訂內容以檢查哪些圖像是正在使用的以及哪些圖像是多余的。確保在運行前創建完整的數據庫和映像備份。",
@@ -63,10 +64,7 @@ return [
     'maint_image_cleanup_success' => '找到並刪除了 :count 張可能未使用的圖像!',
     'maint_image_cleanup_nothing_found' => '找不到未使用的圖像,沒有刪除!',
 
-    /**
-     * Role settings
-     */
-
+    // Role Settings
     'roles' => '角色',
     'role_user_roles' => '使用者角色',
     'role_create' => '建立角色',
@@ -87,6 +85,7 @@ return [
     'role_manage_roles' => '管理角色與角色權限',
     'role_manage_entity_permissions' => '管理所有圖書,章節和頁面的權限',
     'role_manage_own_entity_permissions' => '管理自己的圖書,章節和頁面的權限',
+    'role_manage_page_templates' => 'Manage page templates',
     'role_manage_settings' => '管理App設定',
     'role_asset' => '資源項目',
     'role_asset_desc' => '對系統內資源的預設權限將由這裡的權限控制。若有單獨設定在書本、章節和頁面上的權限,將會覆蓋這裡的權限設定。',
@@ -99,16 +98,22 @@ return [
     'role_users' => '此角色的使用者',
     'role_users_none' => '目前沒有使用者被分配到這個角色',
 
-    /**
-     * Users
-     */
-
+    // Users
     'users' => '使用者',
     'user_profile' => '使用者資料',
     'users_add_new' => '加入使用者',
     'users_search' => '搜尋使用者',
+    'users_details' => 'User Details',
+    'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.',
+    'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.',
     'users_role' => '使用者角色',
+    'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
+    'users_password' => 'User Password',
+    'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 6 characters long.',
+    'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
+    'users_send_invite_option' => 'Send user invite email',
     'users_external_auth_id' => '外部身份驗證ID',
+    'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your LDAP system.',
     'users_password_warning' => '如果您想更改密碼,請填寫以下內容:',
     'users_system_public' => '此使用者代表進入您的App的任何訪客。它不能用於登入,而是自動分配。',
     'users_delete' => '刪除使用者',
@@ -122,10 +127,40 @@ return [
     'users_avatar' => '使用者大頭照',
     'users_avatar_desc' => '目前圖片應該為約256px的正方形。',
     'users_preferred_language' => '語言',
+    'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
     'users_social_accounts' => '社群網站帳號',
     'users_social_accounts_info' => '在這里,您可以連結您的其他帳號,以便方便地登入。如果您選擇解除連結,之後將不能透過此社群網站帳號登入,請設定社群網站帳號來取消本系統p的進入權限。',
     'users_social_connect' => '連結帳號',
     'users_social_disconnect' => '解除連結帳號',
     'users_social_connected' => ':socialAccount 帳號已經成功連結到您的資料。',
     'users_social_disconnected' => ':socialAccount 帳號已經成功解除連結。',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'nl' => 'Nederlands',
+        'pt_BR' => 'Português do Brasil',
+        'sk' => 'Slovensky',
+        'cs' => 'Česky',
+        'sv' => 'Svenska',
+        'ko' => '한국어',
+        'ja' => '日本語',
+        'pl' => 'Polski',
+        'it' => 'Italian',
+        'ru' => 'Русский',
+        'uk' => 'Українська',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+        'hu' => 'Magyar',
+        'tr' => 'Türkçe',
+    ]
+    //!////////////////////////////////
 ];
index 5b708064da65fbb4ee4b2f6cdd8c570479cc5ec6..33a3c7119ac324a2d8f53034760dda41f44e0800 100644 (file)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => ':attribute 需要被同意。',
     'active_url'           => ':attribute 並不是一個有效的網址',
     'after'                => ':attribute 必須是在 :date 後的日期。',
@@ -35,12 +30,41 @@ return [
     'digits'               => ':attribute 必須為:digits位數。',
     'digits_between'       => ':attribute 必須為:min到:max位數。',
     'email'                => ':attribute 必須是有效的電子郵件位址。',
+    'ends_with' => 'The :attribute must end with one of the following: :values',
     'filled'               => ':attribute 字段是必需的。',
+    'gt'                   => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file'    => 'The :attribute must be greater than :value kilobytes.',
+        'string'  => 'The :attribute must be greater than :value characters.',
+        'array'   => 'The :attribute must have more than :value items.',
+    ],
+    'gte'                  => [
+        'numeric' => 'The :attribute must be greater than or equal :value.',
+        'file'    => 'The :attribute must be greater than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be greater than or equal :value characters.',
+        'array'   => 'The :attribute must have :value items or more.',
+    ],
     'exists'               => '選中的 :attribute 無效。',
     'image'                => ':attribute 必須是一個圖片。',
+    'image_extension'      => 'The :attribute must have a valid & supported image extension.',
     'in'                   => '選中的 :attribute 無效。',
     'integer'              => ':attribute 必須是一個整數。',
     'ip'                   => ':attribute 必須是一個有效的IP位址。',
+    'ipv4'                 => 'The :attribute must be a valid IPv4 address.',
+    'ipv6'                 => 'The :attribute must be a valid IPv6 address.',
+    'json'                 => 'The :attribute must be a valid JSON string.',
+    'lt'                   => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file'    => 'The :attribute must be less than :value kilobytes.',
+        'string'  => 'The :attribute must be less than :value characters.',
+        'array'   => 'The :attribute must have less than :value items.',
+    ],
+    'lte'                  => [
+        'numeric' => 'The :attribute must be less than or equal :value.',
+        'file'    => 'The :attribute must be less than or equal :value kilobytes.',
+        'string'  => 'The :attribute must be less than or equal :value characters.',
+        'array'   => 'The :attribute must not have more than :value items.',
+    ],
     'max'                  => [
         'numeric' => ':attribute 不能超過:max。',
         'file'    => ':attribute 不能超過:max KB。',
@@ -54,7 +78,9 @@ return [
         'string'  => ':attribute 至少為:min個字元。',
         'array'   => ':attribute 至少有:min項。',
     ],
+    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => '選中的 :attribute 無效。',
+    'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attribute 必須是一個數。',
     'regex'                => ':attribute 格式無效。',
     'required'             => ':attribute 字段是必需的。',
@@ -74,35 +100,15 @@ return [
     'timezone'             => ':attribute 必須是有效的區域。',
     'unique'               => ':attribute 已經被使用。',
     'url'                  => ':attribute 格式無效。',
+    'uploaded'             => 'The file could not be uploaded. The server may not accept files of this size.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => '需要確認密碼',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
similarity index 98%
rename from resources/assets/sass/_blocks.scss
rename to resources/sass/_blocks.scss
index 032b1cbeb1c467983eaf8da8b1e02c5b0c38d0f2..2cb17a18db90e8f7185ffde7e52973ae1dd249b3 100644 (file)
   line-height: 1;
 }
 
+.card.border-card {
+  border: 1px solid #DDD;
+}
+
 .card.drag-card {
   border: 1px solid #DDD;
   border-radius: 4px;
   }
 }
 
-.bookshelf-grid-item .grid-card-content h2 a  {
-  color: $color-bookshelf;
-  fill: $color-bookshelf;
-}
-
 .book-grid-item .grid-card-footer {
   p.small {
     font-size: .8em;
similarity index 60%
rename from resources/assets/sass/_buttons.scss
rename to resources/sass/_buttons.scss
index eb7a09342ad60079503534da6294393af6a75fbc..e3d9e17cad825beec15792dac617ce3914c45d9b 100644 (file)
@@ -1,29 +1,9 @@
 button {
+  background-color: transparent;
+  border: 0;
   font-size: 100%;
 }
 
-@mixin generate-button-colors($textColor, $backgroundColor) {
-  background-color: $backgroundColor;
-  color: $textColor;
-  fill: $textColor;
-  border: 1px solid $backgroundColor;
-  &:hover {
-    background-color: lighten($backgroundColor, 8%);
-    color: $textColor;
-  }
-  &:active {
-    background-color: darken($backgroundColor, 8%);
-  }
-  &:focus {
-    background-color: lighten($backgroundColor, 4%);
-    box-shadow: $bs-light;
-    color: $textColor;
-  }
-}
-
-// Button Specific Variables
-$button-border-radius: 2px;
-
 .button  {
   text-decoration: none;
   font-size: 0.85rem;
@@ -34,34 +14,54 @@ $button-border-radius: 2px;
   display: inline-block;
   font-weight: 400;
   outline: 0;
-  border-radius: $button-border-radius;
+  border-radius: 2px;
   cursor: pointer;
-  transition: background-color ease-in-out 120ms, box-shadow ease-in-out 120ms;
+  transition: background-color ease-in-out 120ms,
+    filter ease-in-out 120ms,
+    box-shadow ease-in-out 120ms;
   box-shadow: none;
-  background-color: $primary;
+  background-color: var(--color-primary);
   color: #FFF;
   fill: #FFF;
   text-transform: uppercase;
-  border: 1px solid $primary;
+  border: 1px solid var(--color-primary);
   vertical-align: top;
-  &:hover, &:focus {
+  &:hover, &:focus, &:active {
+    background-color: var(--color-primary);
     text-decoration: none;
+    color: #FFFFFF;
+  }
+  &:hover {
+    box-shadow: $bs-light;
+    filter: brightness(110%);
+  }
+  &:focus {
+    outline: 1px dotted currentColor;
+    outline-offset: -$-xs;
+    box-shadow: none;
+    filter: brightness(90%);
   }
   &:active {
-    background-color: darken($primary, 8%);
+    outline: 0;
   }
 }
-.button.primary {
-  @include generate-button-colors(#FFFFFF, $primary);
-}
+
 .button.outline {
   background-color: transparent;
-  color: #888;
-  fill: #888;
-  border: 1px solid #DDD;
+  color: #666;
+  fill: currentColor;
+  border: 1px solid #CCC;
   &:hover, &:focus, &:active {
+    border: 1px solid #CCC;
     box-shadow: none;
-    background-color: #EEE;
+    background-color: #F2F2F2;
+    filter: none;
+  }
+  &:active {
+    border-color: #BBB;
+    background-color: #DDD;
+    color: #666;
+    box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.1);
   }
 }
 
@@ -83,12 +83,18 @@ $button-border-radius: 2px;
   user-select: none;
   font-size: 0.75rem;
   line-height: 1.4em;
-  &:focus, &:active {
+  color: var(--color-primary);
+  fill: var(--color-primary);
+  &:active {
     outline: 0;
   }
   &:hover {
     text-decoration: none;
   }
+  &:hover, &:focus {
+    color: var(--color-primary);
+    fill: var(--color-primary);
+  }
 }
 
 .button.block {
@@ -118,6 +124,7 @@ $button-border-radius: 2px;
 .button[disabled] {
   background-color: #BBB;
   cursor: default;
+  border-color: #CCC;
   &:hover {
     background-color: #BBB;
     cursor: default;
similarity index 52%
rename from resources/assets/sass/_colors.scss
rename to resources/sass/_colors.scss
index 8f2de6c82007599538b126cac2a72ba189e6e5f9..8623d374af4b1f63ffa4a9f8a352b26b1de6247a 100644 (file)
@@ -1,3 +1,13 @@
+/**
+ * Background colors
+ */
+
+.primary-background {
+  background-color: var(--color-primary) !important;
+}
+.primary-background-light {
+  background-color: var(--color-primary-light);
+}
 
 /*
  * Status text colors
  * Style text colors
  */
 .text-primary, .text-primary:hover, .text-primary-hover:hover  {
-  color: $primary !important;
-  fill: $primary !important;
+  color: var(--color-primary) !important;
+  fill: var(--color-primary) !important;
 }
 
 .text-muted {
-  color: lighten($text-dark, 26%) !important;
-  fill: lighten($text-dark, 26%) !important;
-  &.small, .small {
-    color: lighten($text-dark, 32%) !important;
-    fill: lighten($text-dark, 32%) !important;
-  }
+  color: #575757 !important;
+  fill: #575757 !important;
 }
 
 /*
  * Entity text colors
  */
 .text-bookshelf, .text-bookshelf:hover {
-  color: $color-bookshelf;
-  fill: $color-bookshelf;
+  color: var(--color-bookshelf);
+  fill: var(--color-bookshelf);
 }
 .text-book, .text-book:hover {
-  color: $color-book;
-  fill: $color-book;
+  color: var(--color-book);
+  fill: var(--color-book);
 }
 .text-page, .text-page:hover {
-  color: $color-page;
-  fill: $color-page;
+  color: var(--color-page);
+  fill: var(--color-page);
 }
 .text-page.draft, .text-page.draft:hover {
-  color: $color-page-draft;
-  fill: $color-page-draft;
+  color: var(--color-page-draft);
+  fill: var(--color-page-draft);
 }
 .text-chapter, .text-chapter:hover {
-  color: $color-chapter;
-  fill: $color-chapter;
+  color: var(--color-chapter);
+  fill: var(--color-chapter);
 }
 
 /*
   background-color: #FFFFFF;
 }
 .bg-book {
-  background-color: $color-book;
+  background-color: var(--color-book);
 }
 .bg-chapter {
-  background-color: $color-chapter;
+  background-color: var(--color-chapter);
 }
 .bg-shelf {
-  background-color: $color-bookshelf;
+  background-color: var(--color-bookshelf);
 }
\ No newline at end of file
similarity index 92%
rename from resources/assets/sass/_components.scss
rename to resources/sass/_components.scss
index 039ac4dc8d8c3580c7f0374025ac6aa43a0692e3..2085e06ea4c4aeb35e9bcb09d1fdd771eb6808c1 100644 (file)
   .popup-content {
     overflow-y: auto;
   }
+  &:focus {
+    outline: 0;
+  }
 }
 
 .popup-footer button, .popup-header-close {
     padding: 8px $-m;
   }
 }
-.popup-footer {
-  margin-top: 1px;
-}
 body.flexbox-support #entity-selector-wrap .popup-body .form-group {
   height: 444px;
   min-height: 444px;
@@ -579,6 +579,20 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
   }
 }
 
+.nav-tabs {
+  text-align: center;
+  a, .tab-item {
+    padding: $-m;
+    display: inline-block;
+    color: #666;
+    fill: #666;
+    cursor: pointer;
+    &.selected {
+      border-bottom: 2px solid var(--color-primary);
+    }
+  }
+}
+
 .image-picker .none {
   display: none;
 }
@@ -588,7 +602,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
 }
 
 #code-editor .lang-options {
-  max-width: 400px;
+  max-width: 480px;
   margin-bottom: $-s;
   a {
     margin-right: $-xs;
@@ -620,7 +634,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
     opacity: 0;
     transition: opacity ease-in-out 120ms;
   }
-  &:hover .actions {
+  &:hover .actions, &:focus-within .actions {
     opacity: 1;
   }
 }
@@ -637,7 +651,6 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
     }
     a { color: #666; }
     span {
-      color: #888;
       padding-left: $-xxs;
     }
   }
@@ -655,4 +668,32 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
 }
 .permissions-table tr:hover [permissions-table-toggle-all-in-row] {
   display: inline;
+}
+
+.template-item {
+  cursor: pointer;
+  position: relative;
+  &:hover, .template-item-actions button:hover {
+    background-color: #F2F2F2;
+  }
+  .template-item-actions {
+    position: absolute;
+    top: 0;
+    right: 0;
+    width: 50px;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    border-left: 1px solid #DDD;
+  }
+  .template-item-actions button {
+    cursor: pointer;
+    flex: 1;
+    background: #FFF;
+    border: 0;
+    border-top: 1px solid #DDD;
+  }
+  .template-item-actions button:first-child {
+    border-top: 0;
+  }
 }
\ No newline at end of file
similarity index 90%
rename from resources/assets/sass/_forms.scss
rename to resources/sass/_forms.scss
index 5fd19bb1f3b14613803b8ecaea6088ac22a23701..64308b29e725bbe0470189b0e259e31630ee0b73 100644 (file)
@@ -20,7 +20,8 @@
     background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAMUlEQVQIW2NkwAGuXbv2nxGbHEhCS0uLEUMSJgHShCKJLIEiiS4Bl8QmAZbEJQGSBAC62BuJ+tt7zgAAAABJRU5ErkJggg==);
   }
   &:focus {
-    outline: 0;
+    border-color: var(--color-primary);
+    outline: 1px solid var(--color-primary);
   }
 }
 
 }
 
 .markdown-display {
-  padding: 0 $-m 0;
   margin-left: -1px;
-  overflow-y: scroll;
-  &.page-content {
-    margin: 0 auto;
-    width: 100%;
-    max-width: 100%;
+}
+
+.markdown-editor-display {
+  background-color: #FFFFFF;
+  body {
+    background-color: #FFFFFF;
+    padding-left: 16px;
+    padding-right: 16px;
   }
   [drawio-diagram]:hover {
-    outline: 2px solid $primary;
+    outline: 2px solid var(--color-primary);
   }
 }
 
@@ -269,6 +272,9 @@ input[type=color] {
     margin-left: -$-m;
     margin-right: -$-m;
     padding: $-s $-m;
+    display: block;
+    width: calc(100% + 32px);
+    text-align: left;
   }
   .collapse-title, .collapse-title label {
     cursor: pointer;
@@ -342,16 +348,16 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] {
   button {
     background-color: transparent;
     border: none;
-    color: $primary;
+    fill: #666;
     padding: 0;
     cursor: pointer;
     position: absolute;
     left: 8px;
-    top: 9.5px;
+    top: 9px;
   }
   input {
     display: block;
-    padding-left: $-l;
+    padding-left: $-l + 4px;
     width: 300px;
     max-width: 100%;
   }
@@ -380,3 +386,18 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] {
   background-color: #BBB;
   max-width: 100%;
 }
+
+.custom-file-input {
+  overflow: hidden;
+  padding: 0;
+  position: absolute;
+  white-space: nowrap;
+  width: 1px;
+  height: 1px;
+  border: 0;
+  clip: rect(0, 0, 0, 0);
+}
+.custom-file-input:focus + label {
+  border-color: var(--color-primary);
+  outline: 1px solid var(--color-primary);
+}
\ No newline at end of file
similarity index 93%
rename from resources/assets/sass/_header.scss
rename to resources/sass/_header.scss
index adb014f4a956b135255982d0ea71830138ab3ece..687ddd8d2259c83dcf0cb94530e7b34b7d5e9b9d 100644 (file)
@@ -18,7 +18,6 @@ header {
   display: block;
   z-index: 11;
   top: 0;
-  background-color: $primary-dark;
   color: #fff;
   fill: #fff;
   border-bottom: 1px solid #DDD;
@@ -47,9 +46,7 @@ header {
   }
   .user-name {
     vertical-align: top;
-    padding-top: $-m;
     position: relative;
-    top: -3px;
     display: inline-block;
     cursor: pointer;
     > * {
@@ -73,6 +70,9 @@ header {
   }
 }
 
+.header *, .primary-background * {
+  outline-color: #FFF;
+}
 
 
 .header-search {
@@ -88,6 +88,10 @@ header .search-box {
     color: #EEE;
     z-index: 2;
     padding-left: 40px;
+    &:focus {
+      outline: none;
+      border: 1px solid rgba(255, 255, 255, 0.6);
+    }
   }
   button {
     fill: #EEE;
@@ -103,12 +107,6 @@ header .search-box {
   ::-moz-placeholder { /* Firefox 19+ */
     color: #DDD;
   }
-  :-ms-input-placeholder { /* IE 10+ */
-    color: #DDD;
-  }
-  :-moz-placeholder { /* Firefox 18- */
-    color: #DDD;
-  }
   @include between($l, $xl) {
     max-width: 200px;
   }
@@ -243,7 +241,7 @@ header .search-box {
     line-height: 0.8;
     margin: -2px 0 0;
   }
-  &:hover {
+  &:hover, &:focus-within {
     opacity: 1;
   }
 }
@@ -372,18 +370,4 @@ header .search-box {
   .action-buttons .dropdown-container:last-child a {
     padding-left: $-xs;
   }
-}
-
-.nav-tabs {
-  text-align: center;
-  a, .tab-item {
-    padding: $-m;
-    display: inline-block;
-    color: #666;
-    fill: #666;
-    cursor: pointer;
-    &.selected {
-      border-bottom: 2px solid $primary;
-    }
-  }
 }
\ No newline at end of file
similarity index 81%
rename from resources/assets/sass/_html.scss
rename to resources/sass/_html.scss
index 7c3a3c49b41179aaf673b211ed834a399ca8a54e..de48c8ed1bbe8eac685067c399c583faafe0abdc 100644 (file)
@@ -1,5 +1,10 @@
 * {
   box-sizing: border-box;
+  outline-color: #444444;
+}
+
+*:focus {
+  outline-style: dotted;
 }
 
 html {
similarity index 97%
rename from resources/assets/sass/_layout.scss
rename to resources/sass/_layout.scss
index b282b12e272c45455b4123527236d24fe27bacd2..1a7ff2cab029e615c9f7ad7025b4b4fcc8bc2b9a 100644 (file)
@@ -116,6 +116,7 @@ body.flexbox {
   min-height: 0;
   max-width: 100%;
   position: relative;
+  overflow-y: hidden;
 }
 
 .flex {
@@ -301,13 +302,17 @@ body.flexbox {
   .tri-layout-mobile-tabs {
     display: none;
   }
-  .tri-layout-left-contents > div, .tri-layout-right-contents > div {
+  .tri-layout-left-contents > *, .tri-layout-right-contents > * {
     opacity: 0.6;
     transition: opacity ease-in-out 120ms;
     &:hover {
       opacity: 1;
     }
+    &:focus-within {
+      opacity: 1;
+    }
   }
+
 }
 
 @include smaller-than($m) {
similarity index 93%
rename from resources/assets/sass/_lists.scss
rename to resources/sass/_lists.scss
index c413bcd8eecf6fd2c7e1bfae8b4b89777962286e..2e8fa257aebcf270e87bf438b00659ca85efcebd 100644 (file)
@@ -59,6 +59,8 @@
   .chapter-expansion-toggle {
     border-radius: 0 4px 4px 0;
     padding: $-xs $-m;
+    width: 100%;
+    text-align: left;
   }
   .chapter-expansion-toggle:hover {
     background-color: rgba(0, 0, 0, 0.06);
 }
 .sort-box {
   margin-bottom: $-m;
-  border: 2px solid rgba($color-book, 0.6);
   padding: $-m $-xl;
-  border-radius: 4px;
+  position: relative;
+  &::before {
+    pointer-events: none;
+    content: '';
+    border-radius: 4px;
+    opacity: 0.5;
+    border: 2px solid var(--color-book);
+    display: block;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    position: absolute;
+  }
 }
 .sort-box-options {
   display: flex;
     border: 1px solid #DDD;
     margin-top: -1px;
     min-height: 38px;
-    &.text-chapter {
-      border-left: 2px solid $color-chapter;
-    }
-    &.text-page {
-      border-left: 2px solid $color-page;
-    }
+  }
+  li.text-page, li.text-chapter {
+    border-left: 2px solid currentColor;
   }
   li:first-child {
     margin-top: $-xs;
   display: grid;
   grid-template-columns: min-content 1fr;
   grid-column-gap: $-m;
-  color: #888;
-  fill: #888;
   font-size: 0.9em;
 }
 .card .activity-list-item {
@@ -361,8 +370,8 @@ ul.pagination {
     margin-top: 0;
   }
   .page.draft .text-page {
-    color: $color-page-draft;
-    fill: $color-page-draft;
+    color: var(--color-page-draft);
+    fill: var(--color-page-draft);
   }
   > .dropdown-container {
     display: block;
@@ -416,6 +425,11 @@ ul.pagination {
     background-color: transparent;
     border-color: rgba(0, 0, 0, 0.1);
   }
+  &:focus {
+    background-color: #eee;
+    outline: 1px dotted #666;
+    outline-offset: -2px;
+  }
 }
 
 .entity-list-item-path-sep {
@@ -551,11 +565,16 @@ ul.pagination {
     display: block;
     padding: $-xs $-m;
     color: #555;
-    fill: #555;
+    fill: currentColor;
     white-space: nowrap;
-    &:hover {
+    &:hover, &:focus {
       text-decoration: none;
-      background-color: #EEE;
+      background-color: var(--color-primary-light);
+      color: var(--color-primary);
+    }
+    &:focus {
+      outline: 1px solid var(--color-primary);
+      outline-offset: -2px;
     }
     svg {
       margin-right: $-s;
similarity index 98%
rename from resources/assets/sass/_pages.scss
rename to resources/sass/_pages.scss
index fc784eebe84bcf5a6d745cc5cb3be0ef8048cc2d..709b1a7efe845b85dd6c55027d6d7be2fd5d3f37 100755 (executable)
@@ -207,7 +207,6 @@ body.mce-fullscreen .page-editor .edit-area {
   }
   a.button {
     margin: 0;
-    color: #FFF;
   }
   .svg-icon {
     width: 1.2em;
@@ -258,15 +257,15 @@ body.mce-fullscreen .page-editor .edit-area {
     padding: 0;
     margin: 0;
   }
-  .tabs > span {
+  .tabs > button {
     display: block;
     cursor: pointer;
     padding: $-s $-m;
-    font-size: 13.5px;
+    font-size: 16px;
     line-height: 1.6;
     border-bottom: 1px solid rgba(255, 255, 255, 0.3);
   }
-  &.open .tabs > span.active {
+  &.open .tabs > button.active {
     fill: #444;
     background-color: rgba(0, 0, 0, 0.1);
   }
similarity index 94%
rename from resources/assets/sass/_text.scss
rename to resources/sass/_text.scss
index f1d165a47f991ddb94e31f20bcf88a86b9fa94fe..cf78c162b95c72fc66e9643982bbc4c5aec90ec3 100644 (file)
@@ -90,14 +90,14 @@ h2.list-heading {
  * Link styling
  */
 a {
-  color: $primary;
+  color: var(--color-primary);
+  fill: var(--color-primary);
   cursor: pointer;
   text-decoration: none;
-  transition: color ease-in-out 80ms;
+  transition: filter ease-in-out 80ms;
   line-height: 1.6;
   &:hover {
     text-decoration: underline;
-    color: darken($primary, 20%);
   }
   &.icon {
     display: inline-block;
@@ -106,6 +106,10 @@ a {
     position: relative;
     display: inline-block;
   }
+  &:focus img:only-child {
+    outline: 2px dashed var(--color-primary);
+    outline-offset: 2px;
+  }
 }
 
 .blended-links a {
@@ -195,7 +199,7 @@ pre {
 blockquote {
   display: block;
   position: relative;
-  border-left: 4px solid $primary;
+  border-left: 4px solid var(--color-primary);
   background-color: #F8F8F8;
   padding: $-s $-m $-s $-xl;
   &:before {
@@ -220,7 +224,7 @@ code {
   @extend .code-base;
   display: inline;
   padding: 1px 3px;
-  white-space:pre;
+  white-space:pre-wrap;
   line-height: 1.2em;
   margin-bottom: 1.2em;
 }
@@ -239,7 +243,6 @@ pre code {
 }
 
 span.highlight {
-  //background-color: rgba($primary, 0.2);
   font-weight: bold;
   padding: 2px 4px;
 }
@@ -366,4 +369,3 @@ span.sep {
   margin-right: $-xs;
   pointer-events: none;
 }
-
similarity index 98%
rename from resources/assets/sass/_tinymce.scss
rename to resources/sass/_tinymce.scss
index 4c50f14d2f020ed371b0e88a9ccdb4cb2fb5e10a..27c3b28d01b50874e035ce055362e3784f64967b 100644 (file)
@@ -61,6 +61,7 @@
 
 .page-content.mce-content-body {
   padding-top: 16px;
+  outline: none;
 }
 
 // Fix to prevent 'No color' option from not being clickable.
similarity index 79%
rename from resources/assets/sass/_variables.scss
rename to resources/sass/_variables.scss
index 041b70edfdb4c6a9946dc6aa0705fac46e0b30b1..2d4d3970af2d028b98f825cf2b5833395db81fb1 100644 (file)
@@ -41,21 +41,21 @@ $fs-m: 14px;
 $fs-s: 12px;
 
 // Colours
-$primary: #0288D1;
-$primary-dark: #0288D1;
-$secondary: #cf4d03;
+:root {
+  --color-primary: #206ea7;
+  --color-primary-light: rgba(32,110,167,0.15);
+
+  --color-page: #206ea7;
+  --color-page-draft: #7e50b1;
+  --color-chapter: #af4d0d;
+  --color-book: #077b70;
+  --color-bookshelf: #a94747;
+}
+
 $positive: #0f7d15;
 $negative: #ab0f0e;
-$info: $primary;
-$warning: $secondary;
-$primary-faded: rgba(21, 101, 192, 0.15);
-
-// Item Colors
-$color-bookshelf: #af5a5a;
-$color-book: #009688;
-$color-chapter: #d7804a;
-$color-page: $primary;
-$color-page-draft: #9A60DA;
+$info: #0288D1;
+$warning: #cf4d03;
 
 // Text colours
 $text-dark: #444;
similarity index 97%
rename from resources/assets/sass/export-styles.scss
rename to resources/sass/export-styles.scss
index 4cc782dc0e9d4881d533c55ebc518bef42dd4a6e..958b788075cb50b788685345bfbe9d9b40b111ff 100644 (file)
 @import "lists";
 @import "pages";
 
+
+html, body {
+  background-color: #FFF;
+}
+
 body {
   font-family: 'DejaVu Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Roboto", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
-  background-color: #FFF;
   margin: 0;
   padding: 0;
 }
diff --git a/resources/sass/print-styles.scss b/resources/sass/print-styles.scss
new file mode 100644 (file)
index 0000000..296afbe
--- /dev/null
@@ -0,0 +1,35 @@
+@import "variables";
+
+header {
+  display: none;
+}
+
+html, body {
+  font-size: 12px;
+  background-color: #FFF;
+}
+
+.page-content {
+  margin: 0 auto;
+}
+
+.print-hidden {
+  display: none !important;
+}
+
+.tri-layout-container {
+  grid-template-columns: 1fr;
+  grid-template-areas: "b";
+  margin-left: 0;
+  margin-right: 0;
+  display: block;
+}
+
+.card {
+  box-shadow: none;
+}
+
+.content-wrap.card {
+  padding-left: 0;
+  padding-right: 0;
+}
\ No newline at end of file
similarity index 93%
rename from resources/assets/sass/styles.scss
rename to resources/sass/styles.scss
index 70f04f3ff52677a9d8639816536fae73f9b02630..1f4d00f6b9606b1ebe15d94b0c5b8788dd747b6e 100644 (file)
@@ -75,17 +75,17 @@ $loadingSize: 10px;
     animation-iteration-count: infinite;
     animation-timing-function: cubic-bezier(.62, .28, .23, .99);
     margin-right: 4px;
-    background-color: $color-page;
+    background-color: var(--color-page);
     animation-delay: 0.3s;
   }
   > div:first-child {
       left: -($loadingSize+$-xs);
-      background-color: $color-book;
+      background-color: var(--color-book);
       animation-delay: 0s;
   }
   > div:last-of-type {
     left: $loadingSize+$-xs;
-    background-color: $color-chapter;
+    background-color: var(--color-chapter);
     animation-delay: 0.6s;
   }
   > span {
@@ -99,7 +99,7 @@ $loadingSize: 10px;
 // Back to top link
 $btt-size: 40px;
 [back-to-top] {
-  background-color: $primary;
+  background-color: var(--color-primary);
   position: fixed;
   bottom: $-m;
   right: $-l;
@@ -187,7 +187,7 @@ $btt-size: 40px;
     margin-bottom: 0;
   }
   .entity-list-item.selected {
-    background-color: rgba(0, 0, 0, 0.15) !important;
+    background-color: rgba(0, 0, 0, 0.05) !important;
   }
   .loading {
     height: 400px;
@@ -253,14 +253,15 @@ $btt-size: 40px;
   .list-sort {
     display: inline-grid;
     margin-left: $-s;
-    grid-template-columns: 120px 40px;
+    grid-template-columns: minmax(120px, max-content) 40px;
+    font-size: 0.9rem;
     border: 2px solid #DDD;
     border-radius: 4px;
   }
   .list-sort-label {
     font-weight: bold;
     display: inline-block;
-    color: #888;
+    color: #555;
   }
   .list-sort-type {
     text-align: left;
index f7903e18c72de72fd979356726142b91afff1a92..2699fda00ac59801e454f748dd339eb8b6c7fac8 100644 (file)
@@ -1,12 +1,12 @@
 <div class="form-group">
     <label for="username">{{ trans('auth.username') }}</label>
-    @include('form.text', ['name' => 'username', 'tabindex' => 1])
+    @include('form.text', ['name' => 'username', 'autofocus' => true])
 </div>
 
 @if(session('request-email', false) === true)
     <div class="form-group">
         <label for="email">{{ trans('auth.email') }}</label>
-        @include('form.text', ['name' => 'email', 'tabindex' => 1])
+        @include('form.text', ['name' => 'email'])
         <span class="text-neg">
             {{ trans('auth.ldap_email_hint') }}
         </span>
@@ -15,5 +15,5 @@
 
 <div class="form-group">
     <label for="password">{{ trans('auth.password') }}</label>
-    @include('form.password', ['name' => 'password', 'tabindex' => 1])
+    @include('form.password', ['name' => 'password'])
 </div>
\ No newline at end of file
index dc6081637f784f9a7823d9443eca91d51a0e7f85..52fae3750ad38dbd9cccacedeea82328d63fd0b1 100644 (file)
@@ -1,11 +1,11 @@
 <div class="form-group">
     <label for="email">{{ trans('auth.email') }}</label>
-    @include('form.text', ['name' => 'email', 'tabindex' => 1])
+    @include('form.text', ['name' => 'email', 'autofocus' => true])
 </div>
 
 <div class="form-group">
     <label for="password">{{ trans('auth.password') }}</label>
-    @include('form.password', ['name' => 'password', 'tabindex' => 1])
+    @include('form.password', ['name' => 'password'])
     <span class="block small mt-s">
         <a href="{{ url('/password/email') }}">{{ trans('auth.forgot_password') }}</a>
     </span>
diff --git a/resources/views/auth/invite-set-password.blade.php b/resources/views/auth/invite-set-password.blade.php
new file mode 100644 (file)
index 0000000..fbe62f2
--- /dev/null
@@ -0,0 +1,27 @@
+@extends('simple-layout')
+
+@section('content')
+
+    <div class="container very-small mt-xl">
+        <div class="card content-wrap auto-height">
+            <h1 class="list-heading">{{ trans('auth.user_invite_page_welcome', ['appName' => setting('app-name')]) }}</h1>
+            <p>{{ trans('auth.user_invite_page_text', ['appName' => setting('app-name')]) }}</p>
+
+            <form action="{{ url('/register/invite/' . $token) }}" method="POST" class="stretch-inputs">
+                {!! csrf_field() !!}
+
+                <div class="form-group">
+                    <label for="password">{{ trans('auth.password') }}</label>
+                    @include('form.password', ['name' => 'password', 'placeholder' => trans('auth.password_hint')])
+                </div>
+
+                <div class="text-right">
+                    <button class="button">{{ trans('auth.user_invite_page_confirm_button') }}</button>
+                </div>
+
+            </form>
+
+        </div>
+    </div>
+
+@stop
index 8d89c128885d98d47aa83d42a5ec1100f4d422fc..7e4a3992b3510a4f28927c7b46176c041f3263ee 100644 (file)
@@ -7,7 +7,7 @@
         <div class="my-l">&nbsp;</div>
 
         <div class="card content-wrap auto-height">
-            <h1 class="list-heading">{{ title_case(trans('auth.log_in')) }}</h1>
+            <h1 class="list-heading">{{ Str::title(trans('auth.log_in')) }}</h1>
 
             <form action="{{ url('/login') }}" method="POST" id="login-form" class="mt-l">
                 {!! csrf_field() !!}
                             'name' => 'remember',
                             'checked' => false,
                             'value' => 'on',
-                            'tabindex' => 1,
                             'label' => trans('auth.remember_me'),
                         ])
                     </div>
 
                     <div class="text-right">
-                        <button class="button primary" tabindex="1">{{ title_case(trans('auth.log_in')) }}</button>
+                        <button class="button">{{ Str::title(trans('auth.log_in')) }}</button>
                     </div>
                 </div>
 
index 864b4e7d26b3940f6a4491f5519007126ebc587f..8273ed2356bf931f7fbe142fd6e4ad1e4841baec 100644 (file)
@@ -16,7 +16,7 @@
                 </div>
 
                 <div class="from-group text-right mt-m">
-                    <button class="button primary">{{ trans('auth.reset_password_send_button') }}</button>
+                    <button class="button">{{ trans('auth.reset_password_send_button') }}</button>
                 </div>
             </form>
 
index 227b39079d75879b517311c7999dd7071b041c21..930544cde40e870c788c1793afeb70661bc010a2 100644 (file)
@@ -26,7 +26,7 @@
                 </div>
 
                 <div class="from-group text-right mt-m">
-                    <button class="button primary">{{ trans('auth.reset_password') }}</button>
+                    <button class="button">{{ trans('auth.reset_password') }}</button>
                 </div>
             </form>
 
index 9cf34f501e0170571ac6ca2971c3a988889256e3..0e996a00d2300e1d27ecba57976260af441e6ee0 100644 (file)
@@ -6,7 +6,7 @@
         <div class="my-l">&nbsp;</div>
 
         <div class="card content-wrap auto-height">
-            <h1 class="list-heading">{{ title_case(trans('auth.sign_up')) }}</h1>
+            <h1 class="list-heading">{{ Str::title(trans('auth.sign_up')) }}</h1>
 
             <form action="{{ url("/register") }}" method="POST" class="mt-l stretch-inputs">
                 {!! csrf_field() !!}
@@ -31,7 +31,7 @@
                         <a href="{{ url('/login') }}">{{ trans('auth.already_have_account') }}</a>
                     </div>
                     <div class="from-group text-right">
-                        <button class="button primary">{{ trans('auth.create_account') }}</button>
+                        <button class="button">{{ trans('auth.create_account') }}</button>
                     </div>
                 </div>
 
index 2142a5dcb4afead277b8da223ef58059ec639d29..85473685b96207e108d07d3093a2c23fa4f43bcd 100644 (file)
@@ -24,7 +24,7 @@
                     @endif
                 </div>
                 <div class="form-group text-right mt-m">
-                    <button type="submit" class="button primary">{{ trans('auth.email_not_confirmed_resend_button') }}</button>
+                    <button type="submit" class="button">{{ trans('auth.email_not_confirmed_resend_button') }}</button>
                 </div>
             </form>
 
index da0e6eb44601fb2104a0410a36b2149a4d87c6be..07548162067404fa82061695c6753fe5bd15e27c 100644 (file)
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html class="@yield('body-class')">
+<html lang="{{ config('app.lang') }}" class="@yield('body-class')">
 <head>
     <title>{{ isset($pageTitle) ? $pageTitle . ' | ' : '' }}{{ setting('app-name') }}</title>
 
     @include('partials.notifications')
     @include('common.header')
 
-    <section id="content" class="block">
+    <div id="content" class="block">
         @yield('content')
-    </section>
+    </div>
 
-    <div back-to-top class="primary-background">
+    <div back-to-top class="primary-background print-hidden">
         <div class="inner">
             @icon('chevron-up') <span>{{ trans('common.back_to_top') }}</span>
         </div>
index 65958e137e8aa357fb1613115e86edade88c8566..db3e90e51913a8d6870417a1687984107a050ec7 100644 (file)
             @endif
         </div>
 
-        <div class="content-wrap card">
+        <main class="content-wrap card">
             <h1 class="list-heading">{{ trans('entities.books_create') }}</h1>
             <form action="{{ isset($bookshelf) ? $bookshelf->getUrl('/create-book') : url('/books') }}" method="POST" enctype="multipart/form-data">
-                @include('books.form')
+                @include('books.form', ['returnLocation' => isset($bookshelf) ? $bookshelf->getUrl() : url('/books')])
             </form>
-        </div>
+        </main>
     </div>
 
 @stop
\ No newline at end of file
index 2860e8bcdf15a63dbb2dec9cee399c4f39020ea0..be3f742cba5d13f5bc45c01864d3122ba4043283 100644 (file)
@@ -23,7 +23,7 @@
                 {!! csrf_field() !!}
                 <input type="hidden" name="_method" value="DELETE">
                 <a href="{{$book->getUrl()}}" class="button outline">{{ trans('common.cancel') }}</a>
-                <button type="submit" class="button primary">{{ trans('common.confirm') }}</button>
+                <button type="submit" class="button">{{ trans('common.confirm') }}</button>
             </form>
         </div>
 
index 2e51ed6e956fb947b905b97282d895ef38f1137c..ac11b58e201df206b3a8cc4b58d8422a16606877 100644 (file)
             ]])
         </div>
 
-        <div class="content-wrap card">
+        <main class="content-wrap card">
             <h1 class="list-heading">{{ trans('entities.books_edit') }}</h1>
             <form action="{{ $book->getUrl() }}" method="POST" enctype="multipart/form-data">
                 <input type="hidden" name="_method" value="PUT">
-                @include('books.form', ['model' => $book])
+                @include('books.form', ['model' => $book, 'returnLocation' => $book->getUrl()])
             </form>
-        </div>
+        </main>
     </div>
 @stop
\ No newline at end of file
index 61c16c72dc586282f298cb0c2f98421dd0c3f688..1cf91046df1b5ab2043c5e7072ed265c9a9c5a89 100644 (file)
@@ -1,5 +1,5 @@
 <!doctype html>
-<html lang="en">
+<html lang="{{ config('app.lang') }}">
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
     <title>{{ $book->name }}</title>
@@ -58,7 +58,7 @@
         <h1 id="{{$bookChild->getType()}}-{{$bookChild->id}}">{{ $bookChild->name }}</h1>
 
         @if($bookChild->isA('chapter'))
-            <p>{{ $bookChild->text }}</p>
+            <p>{{ $bookChild->description }}</p>
 
             @if(count($bookChild->pages) > 0)
                 @foreach($bookChild->pages as $page)
index 5d3f11e2e86e85c12ba78ac360a6a19807df610f..a3235036e02c81c76bfefa799787d3cfd27bf7e3 100644 (file)
@@ -11,9 +11,9 @@
 </div>
 
 <div class="form-group" collapsible id="logo-control">
-    <div class="collapse-title text-primary" collapsible-trigger>
-        <label for="user-avatar">{{ trans('common.cover_image') }}</label>
-    </div>
+    <button type="button" class="collapse-title text-primary" collapsible-trigger aria-expanded="false">
+        <label>{{ trans('common.cover_image') }}</label>
+    </button>
     <div class="collapse-content" collapsible-content>
         <p class="small">{{ trans('common.cover_image_description') }}</p>
 
 </div>
 
 <div class="form-group" collapsible id="tags-control">
-    <div class="collapse-title text-primary" collapsible-trigger>
+    <button type="button" class="collapse-title text-primary" collapsible-trigger aria-expanded="false">
         <label for="tag-manager">{{ trans('entities.book_tags') }}</label>
-    </div>
+    </button>
     <div class="collapse-content" collapsible-content>
-        @include('components.tag-manager', ['entity' => isset($book)?$book:null, 'entityType' => 'chapter'])
+        @include('components.tag-manager', ['entity' => $book ?? null, 'entityType' => 'chapter'])
     </div>
 </div>
 
 <div class="form-group text-right">
-    <a href="{{ isset($book) ? $book->getUrl() : url('/books') }}" class="button outline">{{ trans('common.cancel') }}</a>
-    <button type="submit" class="button primary">{{ trans('entities.books_save') }}</button>
+    <a href="{{ $returnLocation }}" class="button outline">{{ trans('common.cancel') }}</a>
+    <button type="submit" class="button">{{ trans('entities.books_save') }}</button>
 </div>
\ No newline at end of file
index 84578e3a59ba5851ca4224143ccf9c96c8e6b10a..42a2757f94e0e949d536791f6dd79f4daa5ea38a 100644 (file)
@@ -1,10 +1,14 @@
 
-<div class="content-wrap mt-m card">
+<main class="content-wrap mt-m card">
     <div class="grid half v-center no-row-gap">
         <h1 class="list-heading">{{ trans('entities.books') }}</h1>
         <div class="text-m-right my-m">
 
-            @include('partials.sort', ['options' => $sortOptions, 'order' => $order, 'sort' => $sort, 'type' => 'books'])
+            @include('partials.sort', ['options' => [
+                'name' => trans('common.sort_name'),
+                'created_at' => trans('common.sort_created_at'),
+                'updated_at' => trans('common.sort_updated_at'),
+            ], 'order' => $order, 'sort' => $sort, 'type' => 'books'])
 
         </div>
     </div>
@@ -31,4 +35,4 @@
             <a href="{{ url("/create-book") }}" class="text-pos">@icon('edit'){{ trans('entities.create_now') }}</a>
         @endif
     @endif
-</div>
\ No newline at end of file
+</main>
\ No newline at end of file
index 64322cf859696af63971a57b76abad784ac14be9..b387ed6c7c94b082800b1a72979c61668308a2f7 100644 (file)
             ]])
         </div>
 
-        <div class="card content-wrap">
+        <main class="card content-wrap">
             <h1 class="list-heading">{{ trans('entities.books_permissions') }}</h1>
             @include('form.entity-permissions', ['model' => $book])
-        </div>
+        </main>
     </div>
 
 @stop
index b709b29dcd7ac6ac2d1e0512ec62f0d69845c745..cbafdb4364b0d18e350369d83e6457775edba5e5 100644 (file)
@@ -14,7 +14,7 @@
         ]])
     </div>
 
-    <div class="content-wrap card">
+    <main class="content-wrap card">
         <h1 class="break-text" v-pre>{{$book->name}}</h1>
         <div class="book-content" v-show="!searching">
             <p class="text-muted" v-pre>{!! nl2br(e($book->description)) !!}</p>
@@ -53,7 +53,7 @@
         </div>
 
         @include('partials.entity-dashboard-search-results')
-    </div>
+    </main>
 
 @stop
 
 
             <hr class="primary-background">
 
-            <div dropdown class="dropdown-container">
-                <div dropdown-toggle class="icon-list-item">
-                    <span>@icon('export')</span>
-                    <span>{{ trans('entities.export') }}</span>
-                </div>
-                <ul class="wide dropdown-menu">
-                    <li><a href="{{ $book->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
-                    <li><a href="{{ $book->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
-                    <li><a href="{{ $book->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
-                </ul>
-            </div>
+            @include('partials.entity-export-menu', ['entity' => $book])
         </div>
     </div>
 
index 676e7112e574fea4e2cae200cdd09559473a0218..642b88c873a1c442451494903a5db83db76c5c33 100644 (file)
                         <input book-sort-input type="hidden" name="sort-tree">
                         <div class="list text-right">
                             <a href="{{ $book->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
-                            <button class="button primary" type="submit">{{ trans('entities.books_sort_save') }}</button>
+                            <button class="button" type="submit">{{ trans('entities.books_sort_save') }}</button>
                         </div>
                     </form>
                 </div>
             </div>
 
             <div>
-                <div class="card content-wrap">
+                <main class="card content-wrap">
                     <h2 class="list-heading mb-m">{{ trans('entities.books_sort_show_other') }}</h2>
 
                     @include('components.entity-selector', ['name' => 'books_list', 'selectorSize' => 'compact', 'entityTypes' => 'book', 'entityPermission' => 'update', 'showAdd' => true])
 
-                </div>
+                </main>
             </div>
         </div>
 
index 36c7f9a243cbc0717a0a5da7637f650e578340f1..6137c34e8fce357653db2583e3a21cb413f01aba 100644 (file)
@@ -1,10 +1,11 @@
 <div class="chapter-child-menu">
-    <p chapter-toggle class="text-muted @if($bookChild->matchesOrContains($current)) open @endif">
+    <button chapter-toggle type="button" aria-expanded="{{ $isOpen ? 'true' : 'false' }}"
+            class="text-muted @if($isOpen) open @endif">
         @icon('caret-right') @icon('page') <span>{{ trans_choice('entities.x_pages', $bookChild->pages->count()) }}</span>
-    </p>
-    <ul class="sub-menu inset-list @if($bookChild->matchesOrContains($current)) open @endif">
+    </button>
+    <ul class="sub-menu inset-list @if($isOpen) open @endif" @if($isOpen) style="display: block;" @endif role="menu">
         @foreach($bookChild->pages as $childPage)
-            <li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}">
+            <li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}" role="presentation">
                 @include('partials.entity-list-item-basic', ['entity' => $childPage, 'classes' => $current->matches($childPage)? 'selected' : '' ])
             </li>
         @endforeach
index fd2c82b46563e507f4b9647ddfb15fcc9ba8ac59..c9787e6348991073a5c3e265097e0926b8179338 100644 (file)
             ]])
         </div>
 
-        <div class="content-wrap card">
+        <main class="content-wrap card">
             <h1 class="list-heading">{{ trans('entities.chapters_create') }}</h1>
             <form action="{{ $book->getUrl('/create-chapter') }}" method="POST">
                 @include('chapters.form')
             </form>
-        </div>
+        </main>
 
     </div>
 @stop
\ No newline at end of file
index 3444ee0fb19271f242352621d4c5ea3cd5799607..60f8c99339022535b48019d4232b3a764c831574 100644 (file)
@@ -27,7 +27,7 @@
 
                 <div class="text-right">
                     <a href="{{ $chapter->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
-                    <button type="submit" class="button primary">{{ trans('common.confirm') }}</button>
+                    <button type="submit" class="button">{{ trans('common.confirm') }}</button>
                 </div>
             </form>
         </div>
index d282fe1ddc9b614d5d40aede5f093dcacefa6792..d8bb056f632ce8d172da67ebb8b2e50ad7c092cb 100644 (file)
             ]])
         </div>
 
-        <div class="content-wrap card">
+        <main class="content-wrap card">
             <h1 class="list-heading">{{ trans('entities.chapters_edit') }}</h1>
             <form action="{{  $chapter->getUrl() }}" method="POST">
                 <input type="hidden" name="_method" value="PUT">
                 @include('chapters.form', ['model' => $chapter])
             </form>
-        </div>
+        </main>
 
     </div>
 
index 2830855b4f8a0fc53a132ebda8cc9c19b5e818e9..580c123ccf6d7901491f80e943ff03a264ae3bb6 100644 (file)
@@ -1,5 +1,5 @@
 <!doctype html>
-<html lang="en">
+<html lang="{{ config('app.lang') }}">
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
     <title>{{ $chapter->name }}</title>
index 31d8597de44c60681153908ed9f834bd837056d4..cd240e685dd651f5501a4659e74d8e145c0c1760 100644 (file)
@@ -12,9 +12,9 @@
 </div>
 
 <div class="form-group" collapsible id="logo-control">
-    <div class="collapse-title text-primary" collapsible-trigger>
-        <label for="user-avatar">{{ trans('entities.chapter_tags') }}</label>
-    </div>
+    <button type="button" class="collapse-title text-primary" collapsible-trigger aria-expanded="false">
+        <label for="tags">{{ trans('entities.chapter_tags') }}</label>
+    </button>
     <div class="collapse-content" collapsible-content>
         @include('components.tag-manager', ['entity' => isset($chapter)?$chapter:null, 'entityType' => 'chapter'])
     </div>
@@ -22,5 +22,5 @@
 
 <div class="form-group text-right">
     <a href="{{ isset($chapter) ? $chapter->getUrl() : $book->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
-    <button type="submit" class="button primary">{{ trans('entities.chapters_save') }}</button>
+    <button type="submit" class="button">{{ trans('entities.chapters_save') }}</button>
 </div>
index fd463e07a4fbfc51945c45678f2761e22cd8ef9f..7e2e0e1c539c9dca666540a245c5cf2342ea9384 100644 (file)
@@ -11,7 +11,9 @@
     <div class="chapter chapter-expansion">
         <span class="icon text-chapter">@icon('page')</span>
         <div class="content">
-            <div chapter-toggle class="text-muted chapter-expansion-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->pages->count()) }}</span></div>
+            <button type="button" chapter-toggle
+                    aria-expanded="false"
+                    class="text-muted chapter-expansion-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->pages->count()) }}</span></button>
             <div class="inset-list">
                 <div class="entity-list-item-children">
                     @include('partials.entity-list', ['entities' => $chapter->pages])
index 7f3de1322bdcaf94200ef5dfbeddbb358f190bf3..8663dca5050e53fb0dbbc9ab6b369d720bbab59a 100644 (file)
@@ -15,7 +15,7 @@
             ]])
         </div>
 
-        <div class="card content-wrap">
+        <main class="card content-wrap">
             <h1 class="list-heading">{{ trans('entities.chapters_move') }}</h1>
 
             <form action="{{ $chapter->getUrl('/move') }}" method="POST">
 
                 <div class="form-group text-right">
                     <a href="{{ $chapter->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
-                    <button type="submit" class="button primary">{{ trans('entities.chapters_move') }}</button>
+                    <button type="submit" class="button">{{ trans('entities.chapters_move') }}</button>
                 </div>
             </form>
 
-        </div>
+        </main>
 
 
 
index cb5808e7d5070f9fa0c3e2b0dfc1fa273248c24b..48c954dc96871b0f23154772ec2ba6e79a600d80 100644 (file)
             ]])
         </div>
 
-        <div class="card content-wrap">
+        <main class="card content-wrap">
             <h1 class="list-heading">{{ trans('entities.chapters_permissions') }}</h1>
             @include('form.entity-permissions', ['model' => $chapter])
-        </div>
+        </main>
     </div>
 
 @stop
index 0915f9a16120afd97a9a9af1e4f0f0ed278fd36d..105cda760ff496c909aadf723c274fd12b64637b 100644 (file)
@@ -8,14 +8,14 @@
 
 @section('body')
 
-    <div class="mb-m">
+    <div class="mb-m print-hidden">
         @include('partials.breadcrumbs', ['crumbs' => [
             $chapter->book,
             $chapter,
         ]])
     </div>
 
-    <div class="content-wrap card">
+    <main class="content-wrap card">
         <h1 class="break-text" v-pre>{{ $chapter->name }}</h1>
         <div class="chapter-content" v-show="!searching">
             <p v-pre class="text-muted break-text">{!! nl2br(e($chapter->description)) !!}</p>
@@ -50,7 +50,7 @@
         </div>
 
         @include('partials.entity-dashboard-search-results')
-    </div>
+    </main>
 
 @stop
 
 
             <hr class="primary-background"/>
 
-            <div dropdown class="dropdown-container">
-                <div dropdown-toggle class="icon-list-item">
-                    <span>@icon('export')</span>
-                    <span>{{ trans('entities.export') }}</span>
-                </div>
-                <ul class="wide dropdown-menu">
-                    <li><a href="{{ $chapter->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
-                    <li><a href="{{ $chapter->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
-                    <li><a href="{{ $chapter->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
-                </ul>
-            </div>
+            @include('partials.entity-export-menu', ['entity' => $chapter])
         </div>
     </div>
 @stop
index a855031784dc2c181b36691467564064386078c9..ea96a9250dd24a22f2bf274fc093f52edcd6454e 100644 (file)
@@ -1,8 +1,8 @@
 <div class="comment-box mb-m" comment="{{ $comment->id }}" local-id="{{$comment->local_id}}" parent-id="{{$comment->parent_id}}" id="comment{{$comment->local_id}}">
     <div class="header p-s">
-        <div class="grid half no-gap v-center">
-            <div class="meta">
-                <a href="#comment{{$comment->local_id}}" class="text-muted">#{{$comment->local_id}}</a>
+        <div class="grid half left-focus no-gap v-center">
+            <div class="meta text-muted text-small">
+                <a href="#comment{{$comment->local_id}}">#{{$comment->local_id}}</a>
                 &nbsp;&nbsp;
                 @if ($comment->createdBy)
                     <img width="50" src="{{ $comment->createdBy->getAvatar(50) }}" class="avatar" alt="{{ $comment->createdBy->name }}">
             </div>
             <div class="actions text-right">
                 @if(userCan('comment-update', $comment))
-                    <button type="button" class="text-button" action="edit" title="{{ trans('common.edit') }}">@icon('edit')</button>
+                    <button type="button" class="text-button" action="edit" aria-label="{{ trans('common.edit') }}" title="{{ trans('common.edit') }}">@icon('edit')</button>
                 @endif
                 @if(userCan('comment-create-all'))
-                    <button type="button" class="text-button" action="reply" title="{{ trans('common.reply') }}">@icon('reply')</button>
+                    <button type="button" class="text-button" action="reply" aria-label="{{ trans('common.reply') }}" title="{{ trans('common.reply') }}">@icon('reply')</button>
                 @endif
                 @if(userCan('comment-delete', $comment))
                     <div dropdown class="dropdown-container">
-                        <button type="button" dropdown-toggle class="text-button" title="{{ trans('common.delete') }}">@icon('delete')</button>
-                        <ul class="dropdown-menu">
+                        <button type="button" dropdown-toggle aria-haspopup="true" aria-expanded="false" class="text-button" title="{{ trans('common.delete') }}">@icon('delete')</button>
+                        <ul class="dropdown-menu" role="menu">
                             <li class="px-m text-small text-muted pb-s">{{trans('entities.comment_delete_confirm')}}</li>
-                            <li><a action="delete" class="text-button text-neg" >@icon('delete'){{ trans('common.delete') }}</a></li>
+                            <li><button action="delete" type="button" class="text-button text-neg" >@icon('delete'){{ trans('common.delete') }}</button></li>
                         </ul>
                     </div>
                 @endif
@@ -61,7 +61,7 @@
                 </div>
                 <div class="form-group text-right">
                     <button type="button" class="button outline" action="closeUpdateForm">{{ trans('common.cancel') }}</button>
-                    <button type="submit" class="button primary">{{ trans('entities.comment_save') }}</button>
+                    <button type="submit" class="button">{{ trans('entities.comment_save') }}</button>
                 </div>
                 <div class="form-group loading" style="display: none;">
                     @include('partials.loading-icon', ['text' => trans('entities.comment_saving')])
index 99b21b9b263a8e51c1c0c042a7f30c6008003d14..fc81f13ee73053e4c4e208f64cc64e9eb8183826 100644 (file)
@@ -1,4 +1,4 @@
-<div page-comments page-id="{{ $page->id }}" class="comments-list">
+<section page-comments page-id="{{ $page->id }}" class="comments-list" aria-label="{{ trans('entities.comments') }}">
 
     @exposeTranslations([
         'entities.comment_updated_success',
@@ -9,7 +9,7 @@
 
     <div comment-count-bar class="grid half left-focus v-center no-row-gap">
         <h5 comments-title>{{ trans_choice('entities.comment_count', count($page->comments), ['count' => count($page->comments)]) }}</h5>
-        @if (count($page->comments) === 0)
+        @if (count($page->comments) === 0 && userCan('comment-create-all'))
             <div class="text-m-right" comment-add-button-container>
                 <button type="button" action="addComment"
                         class="button outline">{{ trans('entities.comment_add') }}</button>
 
     @if(userCan('comment-create-all'))
         @include('comments.create')
-    @endif
 
-    @if (count($page->comments) > 0)
-        <div class="text-right" comment-add-button-container>
-            <button type="button" action="addComment"
-                    class="button outline">{{ trans('entities.comment_add') }}</button>
-        </div>
+        @if (count($page->comments) > 0)
+            <div class="text-right" comment-add-button-container>
+                <button type="button" action="addComment"
+                        class="button outline">{{ trans('entities.comment_add') }}</button>
+            </div>
+        @endif
     @endif
 
-</div>
\ No newline at end of file
+</section>
\ No newline at end of file
index abd95f008a1e5be44d550f7f1438f1890bdc54ea..61e41a354fab3214883296238e9ee55ac7c9a130 100644 (file)
@@ -19,7 +19,7 @@
             <div class="form-group text-right">
                 <button type="button" class="button outline"
                         action="hideForm">{{ trans('common.cancel') }}</button>
-                <button type="submit" class="button primary">{{ trans('entities.comment_save') }}</button>
+                <button type="submit" class="button">{{ trans('entities.comment_save') }}</button>
             </div>
             <div class="form-group loading" style="display: none;">
                 @include('partials.loading-icon', ['text' => trans('entities.comment_saving')])
index a5336c3f86216e4eb8b6739cd6a597a77668193d..19299695042e98ded5b55735865c31f6fee9528b 100644 (file)
 
         <div class="header-search hide-under-l">
             @if (hasAppAccess())
-            <form action="{{ url('/search') }}" method="GET" class="search-box">
-                <button id="header-search-box-button" type="submit">@icon('search') </button>
-                <input id="header-search-box-input" type="text" name="term" tabindex="2" placeholder="{{ trans('common.search') }}" value="{{ isset($searchTerm) ? $searchTerm : '' }}">
+            <form action="{{ url('/search') }}" method="GET" class="search-box" role="search">
+                <button id="header-search-box-button" type="submit" aria-label="{{ trans('common.search') }}" tabindex="-1">@icon('search') </button>
+                <input id="header-search-box-input" type="text" name="term"
+                       aria-label="{{ trans('common.search') }}" placeholder="{{ trans('common.search') }}"
+                       value="{{ isset($searchTerm) ? $searchTerm : '' }}">
             </form>
             @endif
         </div>
 
         <div class="text-right">
-            <div class="header-links">
+            <nav class="header-links" >
                 <div class="links text-center">
                     @if (hasAppAccess())
                         <a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a>
                 @if(signedInUser())
                     <?php $currentUser = user(); ?>
                     <div class="dropdown-container" dropdown>
-                        <span class="user-name hide-under-l" dropdown-toggle>
+                        <span class="user-name py-s hide-under-l" dropdown-toggle
+                              aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.profile_menu') }}" tabindex="0">
                             <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
                             <span class="name">{{ $currentUser->getShortName(9) }}</span> @icon('caret-down')
                         </span>
-                        <ul class="dropdown-menu">
+                        <ul class="dropdown-menu" role="menu">
                             <li>
                                 <a href="{{ url("/user/{$currentUser->id}") }}">@icon('user'){{ trans('common.view_profile') }}</a>
                             </li>
@@ -66,7 +69,7 @@
                         </ul>
                     </div>
                 @endif
-            </div>
+            </nav>
         </div>
 
     </div>
index c93fa1a24c28c285c740372bf46edd552fa09128..56e281dcb7fd107c52317ba0f856ce9c4310a103 100644 (file)
@@ -2,11 +2,11 @@
 
 @section('body')
     <div class="mt-m">
-        <div class="content-wrap card">
+        <main class="content-wrap card">
             <div class="page-content" page-display="{{ $customHomepage->id }}">
                 @include('pages.page-display', ['page' => $customHomepage])
             </div>
-        </div>
+        </main>
     </div>
 @stop
 
index 7636cd581b63c8c8923ed96ea3101039d54332b0..f8377b1208d49e03c3ac0bad95b7610ebd7cf164 100644 (file)
@@ -1,6 +1,6 @@
 <div id="code-editor">
     <div overlay ref="overlay" v-cloak @click="hide()">
-        <div class="popup-body" @click.stop>
+        <div class="popup-body" tabindex="-1" @click.stop>
 
             <div class="popup-header primary-background">
                 <div class="popup-title">{{ trans('components.code_editor') }}</div>
@@ -18,6 +18,7 @@
                             <a @click="updateLanguage('C#')">C#</a>
                             <a @click="updateLanguage('Go')">Go</a>
                             <a @click="updateLanguage('HTML')">HTML</a>
+                            <a @click="updateLanguage('INI')">INI</a>
                             <a @click="updateLanguage('Java')">Java</a>
                             <a @click="updateLanguage('JavaScript')">JavaScript</a>
                             <a @click="updateLanguage('JSON')">JSON</a>
@@ -43,7 +44,7 @@
                 </div>
 
                 <div class="form-group">
-                    <button type="button" class="button primary" @click="save()">{{ trans('components.code_save') }}</button>
+                    <button type="button" class="button" @click="save()">{{ trans('components.code_save') }}</button>
                 </div>
 
             </div>
index 6ba2f457f88925f6b4dad718fbd176e6bdc9aed7..2bf4e223201a5e04b8accd48b575f7b887f378c5 100644 (file)
@@ -3,12 +3,10 @@ $name
 $value
 $checked
 $label
-$tabindex
 --}}
 <label custom-checkbox class="toggle-switch @if($errors->has($name)) text-neg @endif">
     <input type="checkbox" name="{{$name}}" value="{{ $value }}" @if($checked) checked="checked" @endif>
-    <span tabindex="{{ $tabindex ?? '0' }}"
-          role="checkbox"
+    <span tabindex="0" role="checkbox"
           aria-checked="{{ $checked ? 'true' : 'false' }}"
           class="custom-checkbox text-primary">@icon('check')</span>
     <span class="label">{{$label}}</span>
index c497a16d594563db25c1eb6e0e902857d7814c57..0beee658d88334bd598d79b185dae116fe6aa838 100644 (file)
@@ -1,13 +1,13 @@
 <div id="entity-selector-wrap">
     <div overlay entity-selector-popup>
-        <div class="popup-body small">
+        <div class="popup-body small" tabindex="-1">
             <div class="popup-header primary-background">
                 <div class="popup-title">{{ trans('entities.entity_select') }}</div>
                 <button type="button" class="popup-header-close">x</button>
             </div>
             @include('components.entity-selector', ['name' => 'entity-selector'])
             <div class="popup-footer">
-                <button type="button" disabled="true" class="button entity-link-selector-confirm primary corner-button">{{ trans('common.select') }}</button>
+                <button type="button" disabled="true" class="button entity-link-selector-confirm corner-button">{{ trans('common.select') }}</button>
             </div>
         </div>
     </div>
index 28af63caf3969fb2087729bdd8e1112305f5b4c1..a24f9ac1e9ec79c7d1c55ed95250f177bf9f42ee 100644 (file)
@@ -3,13 +3,13 @@ $target - CSS selector of items to expand
 $key - Unique key for checking existing stored state.
 --}}
 <?php $isOpen = setting()->getForCurrentUser('section_expansion#'. $key); ?>
-<a expand-toggle="{{ $target }}"
+<button type="button" expand-toggle="{{ $target }}"
    expand-toggle-update-endpoint="{{ url('/settings/users/'. $currentUser->id .'/update-expansion-preference/' . $key) }}"
    expand-toggle-is-open="{{ $isOpen ? 'yes' : 'no' }}"
    class="text-muted icon-list-item text-primary">
     <span>@icon('expand-text')</span>
     <span>{{ trans('common.toggle_details') }}</span>
-</a>
+</button>
 @if($isOpen)
     @push('head')
         <style>
index 6781bca5fbbdd462b359336e934adbc3330c08a1..0971c3ed95ed7705da9c476c790d05acea3bc71d 100644 (file)
@@ -9,7 +9,7 @@
     ])
 
     <div overlay v-cloak @click="hide">
-        <div class="popup-body" @click.stop="">
+        <div class="popup-body" tabindex="-1" @click.stop>
 
             <div class="popup-header primary-background">
                 <div class="popup-title">{{ trans('components.image_select') }}</div>
@@ -72,7 +72,7 @@
                                     <button type="button" class="button icon outline" @click="deleteImage">@icon('delete')</button>
 
                                 </div>
-                                <button class="button primary anim fadeIn float right" v-show="selectedImage" @click="callbackAndHide(selectedImage)">
+                                <button class="button anim fadeIn float right" v-show="selectedImage" @click="callbackAndHide(selectedImage)">
                                     {{ trans('components.image_select_image') }}
                                 </button>
                                 <div class="clearfix"></div>
index 73885aeb4548c54aa085c795caed53f081de72f7..9c2661cccbd3d19c412a39af93837ad0be8ab205 100644 (file)
@@ -8,8 +8,8 @@
         </div>
         <div class="text-center">
 
+            <input type="file" class="custom-file-input" accept="image/*" name="{{ $name }}" id="{{ $name }}">
             <label for="{{ $name }}" class="button outline">{{ trans('components.image_select_image') }}</label>
-            <input type="file" class="hidden" accept="image/*" name="{{ $name }}" id="{{ $name }}">
             <input type="hidden" data-reset-input name="{{ $name }}_reset" value="true" disabled="disabled">
             @if(isset($removeName))
                 <input type="hidden" data-remove-input name="{{ $removeName }}" value="{{ $removeValue }}" disabled="disabled">
index 31585dc41aedb8bede78dba2fd7d6b5d43fa4803..2878569374d6db6bd80928a3fe34d8477d982124 100644 (file)
@@ -2,19 +2,18 @@
     <div class="tags">
         <p class="text-muted small">{!! nl2br(e(trans('entities.tags_explain'))) !!}</p>
 
-
         <draggable :options="{handle: '.handle'}" :list="tags" element="div">
             <div v-for="(tag, i) in tags" :key="tag.key" class="card drag-card">
                 <div class="handle" >@icon('grip')</div>
                 <div>
                     <autosuggest url="{{ url('/ajax/tags/suggest/names') }}" type="name" class="outline" :name="getTagFieldName(i, 'name')"
-                                 v-model="tag.name" @input="tagChange(tag)" @blur="tagBlur(tag)" placeholder="{{ trans('entities.tag') }}"/>
+                                 v-model="tag.name" @input="tagChange(tag)" @blur="tagBlur(tag)" placeholder="{{ trans('entities.tag_name') }}"/>
                 </div>
                 <div>
                     <autosuggest url="{{ url('/ajax/tags/suggest/values') }}" type="value" class="outline" :name="getTagFieldName(i, 'value')"
                                  v-model="tag.value" @change="tagChange(tag)" @blur="tagBlur(tag)" placeholder="{{ trans('entities.tag_value') }}"/>
                 </div>
-                <div v-show="tags.length !== 1" class="text-center drag-card-action text-neg" @click="removeTag(tag)">@icon('close')</div>
+                <button type="button" aria-label="{{ trans('entities.tags_remove') }}" v-show="tags.length !== 1" class="text-center drag-card-action text-neg" @click="removeTag(tag)">@icon('close')</button>
             </div>
         </draggable>
 
index f61c076707eb5f1277a8a7d725654e30c4a03816..a5eec30051b32a9bcd9e983c0fd98a40c4356ca9 100644 (file)
@@ -1,8 +1,7 @@
 <label toggle-switch="{{$name}}" custom-checkbox class="toggle-switch">
     <input type="hidden" name="{{$name}}" value="{{$value?'true':'false'}}"/>
     <input type="checkbox" @if($value) checked="checked" @endif>
-    <span tabindex="{{ $tabindex ?? '0' }}"
-          role="checkbox"
+    <span tabindex="0" role="checkbox"
           aria-checked="{{ $value ? 'true' : 'false' }}"
           class="custom-checkbox text-primary">@icon('check')</span>
     <span class="label">{{ $label }}</span>
index c1937ff23e5ce0311ac7247e8bb7a4e57813625f..9c599307ed0279c22de797ea8790717aa91cadf8 100644 (file)
@@ -22,7 +22,7 @@
                 <div class="card mb-xl">
                     <h3 class="card-title">{{ trans('entities.pages_popular') }}</h3>
                     <div class="px-m">
-                        @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, 'page'), 'style' => 'compact'])
+                        @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, ['page']), 'style' => 'compact'])
                     </div>
                 </div>
             </div>
@@ -30,7 +30,7 @@
                 <div class="card mb-xl">
                     <h3 class="card-title">{{ trans('entities.books_popular') }}</h3>
                     <div class="px-m">
-                        @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, 'book'), 'style' => 'compact'])
+                        @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, ['book']), 'style' => 'compact'])
                     </div>
                 </div>
             </div>
@@ -38,7 +38,7 @@
                 <div class="card mb-xl">
                     <h3 class="card-title">{{ trans('entities.chapters_popular') }}</h3>
                     <div class="px-m">
-                        @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, 'chapter'), 'style' => 'compact'])
+                        @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, ['chapter']), 'style' => 'compact'])
                     </div>
                 </div>
             </div>
index b3e148e21c14578f9e7827c3bcd83d9b7ef9096d..3581a545b1c96f6b082b11c3a379747251f13ad1 100644 (file)
@@ -19,7 +19,7 @@
                 <a href="#" permissions-table-toggle-all class="text-small ml-m text-primary">{{ trans('common.toggle_all') }}</a>
             </th>
         </tr>
-        @foreach($roles as $role)
+        @foreach(\BookStack\Auth\Role::restrictable() as $role)
             <tr>
                 <td width="33%" class="pt-m">
                     {{ $role->display_name }}
@@ -37,6 +37,6 @@
 
     <div class="text-right">
         <a href="{{ $model->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
-        <button type="submit" class="button primary">{{ trans('entities.permissions_save') }}</button>
+        <button type="submit" class="button">{{ trans('entities.permissions_save') }}</button>
     </div>
 </form>
\ No newline at end of file
index 8f7c4ee01e5c8384596884f7e390f9d306c293c0..a3c868a8dc08ba293e624f27aa8f1229aa5aa039 100644 (file)
@@ -1,7 +1,6 @@
 <input type="password" id="{{ $name }}" name="{{ $name }}"
        @if($errors->has($name)) class="text-neg" @endif
        @if(isset($placeholder)) placeholder="{{$placeholder}}" @endif
-       @if(isset($tabindex)) tabindex="{{$tabindex}}" @endif
        @if(old($name)) value="{{ old($name)}}" @endif>
 @if($errors->has($name))
     <div class="text-neg text-small">{{ $errors->first($name) }}</div>
index 948a55cbc10a2b6e3f68e16626f8a0ff785394a8..4b3631a06566158d2286df4f0e662b0b4dfd376a 100644 (file)
@@ -1,8 +1,9 @@
 <input type="text" id="{{ $name }}" name="{{ $name }}"
        @if($errors->has($name)) class="text-neg" @endif
        @if(isset($placeholder)) placeholder="{{$placeholder}}" @endif
-       @if(isset($tabindex)) tabindex="{{$tabindex}}" @endif
+       @if($autofocus ?? false) autofocus @endif
+       @if($disabled ?? false) disabled="disabled" @endif
        @if(isset($model) || old($name)) value="{{ old($name) ? old($name) : $model->$name}}" @endif>
 @if($errors->has($name))
     <div class="text-neg text-small">{{ $errors->first($name) }}</div>
-@endif
\ No newline at end of file
+@endif
diff --git a/resources/views/pages/attachment-manager.blade.php b/resources/views/pages/attachment-manager.blade.php
new file mode 100644 (file)
index 0000000..dd00678
--- /dev/null
@@ -0,0 +1,102 @@
+<div toolbox-tab-content="files" id="attachment-manager" page-id="{{ $page->id ?? 0 }}">
+
+    @exposeTranslations([
+    'entities.attachments_file_uploaded',
+    'entities.attachments_file_updated',
+    'entities.attachments_link_attached',
+    'entities.attachments_updated_success',
+    'errors.server_upload_limit',
+    'components.image_upload_remove',
+    'components.file_upload_timeout',
+    ])
+
+    <h4>{{ trans('entities.attachments') }}</h4>
+    <div class="px-l files">
+
+        <div id="file-list" v-show="!fileToEdit">
+            <p class="text-muted small">{{ trans('entities.attachments_explain') }} <span class="text-warn">{{ trans('entities.attachments_explain_instant_save') }}</span></p>
+
+            <div class="tab-container">
+                <div class="nav-tabs">
+                    <button type="button" @click="tab = 'list'" :class="{selected: tab === 'list'}"
+                            class="tab-item">{{ trans('entities.attachments_items') }}</button>
+                    <button type="button" @click="tab = 'file'" :class="{selected: tab === 'file'}"
+                            class="tab-item">{{ trans('entities.attachments_upload') }}</button>
+                    <button type="button" @click="tab = 'link'" :class="{selected: tab === 'link'}"
+                            class="tab-item">{{ trans('entities.attachments_link') }}</button>
+                </div>
+                <div v-show="tab === 'list'">
+                    <draggable style="width: 100%;" :options="{handle: '.handle'}" @change="fileSortUpdate" :list="files" element="div">
+                        <div v-for="(file, index) in files" :key="file.id" class="card drag-card">
+                            <div class="handle">@icon('grip')</div>
+                            <div class="py-s">
+                                <a :href="getFileUrl(file)" target="_blank" v-text="file.name"></a>
+                                <div v-if="file.deleting">
+                                    <span class="text-neg small">{{ trans('entities.attachments_delete_confirm') }}</span>
+                                    <br>
+                                    <button type="button" class="text-primary small" @click="file.deleting = false;">{{ trans('common.cancel') }}</button>
+                                </div>
+                            </div>
+                            <button type="button" @click="startEdit(file)" class="drag-card-action text-center text-primary">@icon('edit')</button>
+                            <button type="button" @click="deleteFile(file)" class="drag-card-action text-center text-neg">@icon('close')</button>
+                        </div>
+                    </draggable>
+                    <p class="small text-muted" v-if="files.length === 0">
+                        {{ trans('entities.attachments_no_files') }}
+                    </p>
+                </div>
+                <div v-show="tab === 'file'">
+                    <dropzone placeholder="{{ trans('entities.attachments_dropzone') }}" :upload-url="getUploadUrl()" :uploaded-to="pageId" @success="uploadSuccess"></dropzone>
+                </div>
+                <div v-show="tab === 'link'" @keypress.enter.prevent="attachNewLink(file)">
+                    <p class="text-muted small">{{ trans('entities.attachments_explain_link') }}</p>
+                    <div class="form-group">
+                        <label for="attachment-via-link">{{ trans('entities.attachments_link_name') }}</label>
+                        <input type="text" placeholder="{{ trans('entities.attachments_link_name') }}" v-model="file.name">
+                        <p class="small text-neg" v-for="error in errors.link.name" v-text="error"></p>
+                    </div>
+                    <div class="form-group">
+                        <label for="attachment-via-link">{{ trans('entities.attachments_link_url') }}</label>
+                        <input type="text"  placeholder="{{ trans('entities.attachments_link_url_hint') }}" v-model="file.link">
+                        <p class="small text-neg" v-for="error in errors.link.link" v-text="error"></p>
+                    </div>
+                    <button @click.prevent="attachNewLink(file)" class="button">{{ trans('entities.attach') }}</button>
+
+                </div>
+            </div>
+
+        </div>
+
+        <div id="file-edit" v-if="fileToEdit" @keypress.enter.prevent="updateFile(fileToEdit)">
+            <h5>{{ trans('entities.attachments_edit_file') }}</h5>
+
+            <div class="form-group">
+                <label for="attachment-name-edit">{{ trans('entities.attachments_edit_file_name') }}</label>
+                <input type="text" id="attachment-name-edit" placeholder="{{ trans('entities.attachments_edit_file_name') }}" v-model="fileToEdit.name">
+                <p class="small text-neg" v-for="error in errors.edit.name" v-text="error"></p>
+            </div>
+
+            <div class="tab-container">
+                <div class="nav-tabs">
+                    <button type="button" @click="editTab = 'file'" :class="{selected: editTab === 'file'}" class="tab-item">{{ trans('entities.attachments_upload') }}</button>
+                    <button type="button" @click="editTab = 'link'" :class="{selected: editTab === 'link'}" class="tab-item">{{ trans('entities.attachments_set_link') }}</button>
+                </div>
+                <div v-if="editTab === 'file'">
+                    <dropzone :upload-url="getUploadUrl(fileToEdit)" :uploaded-to="pageId" placeholder="{{ trans('entities.attachments_edit_drop_upload') }}" @success="uploadSuccessUpdate"></dropzone>
+                    <br>
+                </div>
+                <div v-if="editTab === 'link'">
+                    <div class="form-group">
+                        <label for="attachment-link-edit">{{ trans('entities.attachments_link_url') }}</label>
+                        <input type="text" id="attachment-link-edit" placeholder="{{ trans('entities.attachment_link') }}" v-model="fileToEdit.link">
+                        <p class="small text-neg" v-for="error in errors.edit.link" v-text="error"></p>
+                    </div>
+                </div>
+            </div>
+
+            <button type="button" class="button outline" @click="cancelEdit">{{ trans('common.back') }}</button>
+            <button @click.enter.prevent="updateFile(fileToEdit)" class="button">{{ trans('common.save') }}</button>
+        </div>
+
+    </div>
+</div>
\ No newline at end of file
index f197421725874c893ac27a0c4dd95352346a06b1..0f2af0476e17143b5f8a48df42fccd75d0892f2a 100644 (file)
@@ -29,9 +29,9 @@
                 </div>
 
                 <div class="form-group" collapsible>
-                    <div class="collapse-title text-primary" collapsible-trigger>
+                    <button type="button" class="collapse-title text-primary" collapsible-trigger aria-expanded="false">
                         <label for="entity_selection">{{ trans('entities.pages_copy_desination') }}</label>
-                    </div>
+                    </button>
                     <div class="collapse-content" collapsible-content>
                         @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter', 'entityPermission' => 'page-create'])
                     </div>
@@ -39,7 +39,7 @@
 
                 <div class="form-group text-right">
                     <a href="{{ $page->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
-                    <button type="submit" class="button primary">{{ trans('entities.pages_copy') }}</button>
+                    <button type="submit" class="button">{{ trans('entities.pages_copy') }}</button>
                 </div>
             </form>
 
index a72157a83c45ef6a3dab3d7ab467dc786df2fe10..2ec046fa03bf1bf39b4a89ff45bfefa8ebf58be6 100644 (file)
@@ -34,7 +34,7 @@
                         <input type="hidden" name="_method" value="DELETE">
                         <div class="form-group text-right">
                             <a href="{{ $page->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
-                            <button type="submit" class="button primary">{{ trans('common.confirm') }}</button>
+                            <button type="submit" class="button">{{ trans('common.confirm') }}</button>
                         </div>
                     </form>
                 </div>
index eb2fab94cb3076602b909499ee5a29656c7fe257..c2bbdb53711629d86bc7a3272f8fcab12ff6ba20 100644 (file)
@@ -2,7 +2,7 @@
 
 @section('body')
     <div class="container small pt-xl">
-        <div class="card content-wrap">
+        <main class="card content-wrap">
             <h1 class="list-heading">{{ $title }}</h1>
 
             <div class="book-contents">
@@ -12,6 +12,6 @@
             <div class="text-center">
                 {!! $pages->links() !!}
             </div>
-        </div>
+        </main>
     </div>
 @stop
\ No newline at end of file
index 4930e30a3d5710628cd017d2c7b2350b689b4b37..cfb66fdd0e34ad87827be468764670b230bc2a3b 100644 (file)
@@ -16,7 +16,7 @@
                 <input type="hidden" name="_method" value="PUT">
             @endif
             @include('pages.form', ['model' => $page])
-            @include('pages.form-toolbox')
+            @include('pages.editor-toolbox')
         </form>
     </div>
     
diff --git a/resources/views/pages/editor-toolbox.blade.php b/resources/views/pages/editor-toolbox.blade.php
new file mode 100644 (file)
index 0000000..6ea6518
--- /dev/null
@@ -0,0 +1,32 @@
+<div editor-toolbox class="floating-toolbox">
+
+    <div class="tabs primary-background-light">
+        <button type="button" toolbox-toggle aria-expanded="false">@icon('caret-left-circle')</button>
+        <button type="button" toolbox-tab-button="tags" title="{{ trans('entities.page_tags') }}" class="active">@icon('tag')</button>
+        @if(userCan('attachment-create-all'))
+            <button type="button" toolbox-tab-button="files" title="{{ trans('entities.attachments') }}">@icon('attach')</button>
+        @endif
+        <button type="button" toolbox-tab-button="templates" title="{{ trans('entities.templates') }}">@icon('template')</button>
+    </div>
+
+    <div toolbox-tab-content="tags">
+        <h4>{{ trans('entities.page_tags') }}</h4>
+        <div class="px-l">
+            @include('components.tag-manager', ['entity' => $page, 'entityType' => 'page'])
+        </div>
+    </div>
+
+    @if(userCan('attachment-create-all'))
+        @include('pages.attachment-manager', ['page' => $page])
+    @endif
+
+    <div toolbox-tab-content="templates">
+        <h4>{{ trans('entities.templates') }}</h4>
+
+        <div class="px-l">
+            @include('pages.template-manager', ['page' => $page, 'templates' => $templates])
+        </div>
+
+    </div>
+
+</div>
index e40643c256ff5346124a36dc717c5dada02fadde..4746a56f37842a5f54dfe13cc1c4a1a4d6586b96 100644 (file)
@@ -1,5 +1,5 @@
 <!doctype html>
-<html lang="en">
+<html lang="{{ config('app.lang') }}">
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
     <title>{{ $page->name }}</title>
diff --git a/resources/views/pages/form-toolbox.blade.php b/resources/views/pages/form-toolbox.blade.php
deleted file mode 100644 (file)
index d69be20..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-
-<div editor-toolbox class="floating-toolbox">
-
-    <div class="tabs primary-background-light">
-        <span toolbox-toggle>@icon('caret-left-circle')</span>
-        <span toolbox-tab-button="tags" title="{{ trans('entities.page_tags') }}" class="active">@icon('tag')</span>
-        @if(userCan('attachment-create-all'))
-            <span toolbox-tab-button="files" title="{{ trans('entities.attachments') }}">@icon('attach')</span>
-        @endif
-    </div>
-
-    <div toolbox-tab-content="tags">
-        <h4>{{ trans('entities.page_tags') }}</h4>
-        <div class="px-l">
-            @include('components.tag-manager', ['entity' => $page, 'entityType' => 'page'])
-        </div>
-    </div>
-
-    @if(userCan('attachment-create-all'))
-        <div toolbox-tab-content="files" id="attachment-manager" page-id="{{ $page->id ?? 0 }}">
-
-            @exposeTranslations([
-                'entities.attachments_file_uploaded',
-                'entities.attachments_file_updated',
-                'entities.attachments_link_attached',
-                'entities.attachments_updated_success',
-                'errors.server_upload_limit',
-                'components.image_upload_remove',
-                'components.file_upload_timeout',
-            ])
-
-            <h4>{{ trans('entities.attachments') }}</h4>
-            <div class="px-l files">
-
-                <div id="file-list" v-show="!fileToEdit">
-                    <p class="text-muted small">{{ trans('entities.attachments_explain') }} <span class="text-warn">{{ trans('entities.attachments_explain_instant_save') }}</span></p>
-
-                    <div class="tab-container">
-                        <div class="nav-tabs">
-                            <div @click="tab = 'list'" :class="{selected: tab === 'list'}" class="tab-item">{{ trans('entities.attachments_items') }}</div>
-                            <div @click="tab = 'file'" :class="{selected: tab === 'file'}" class="tab-item">{{ trans('entities.attachments_upload') }}</div>
-                            <div @click="tab = 'link'" :class="{selected: tab === 'link'}" class="tab-item">{{ trans('entities.attachments_link') }}</div>
-                        </div>
-                        <div v-show="tab === 'list'">
-                            <draggable style="width: 100%;" :options="{handle: '.handle'}" @change="fileSortUpdate" :list="files" element="div">
-                                <div v-for="(file, index) in files" :key="file.id" class="card drag-card">
-                                    <div class="handle">@icon('grip')</div>
-                                    <div class="py-s">
-                                        <a :href="getFileUrl(file)" target="_blank" v-text="file.name"></a>
-                                        <div v-if="file.deleting">
-                                            <span class="text-neg small">{{ trans('entities.attachments_delete_confirm') }}</span>
-                                            <br>
-                                            <span class="text-primary small" @click="file.deleting = false;">{{ trans('common.cancel') }}</span>
-                                        </div>
-                                    </div>
-                                    <div @click="startEdit(file)" class="drag-card-action text-center text-primary">@icon('edit')</div>
-                                    <div @click="deleteFile(file)" class="drag-card-action text-center text-neg">@icon('close')</div>
-                                </div>
-                            </draggable>
-                            <p class="small text-muted" v-if="files.length === 0">
-                                {{ trans('entities.attachments_no_files') }}
-                            </p>
-                        </div>
-                        <div v-show="tab === 'file'">
-                            <dropzone placeholder="{{ trans('entities.attachments_dropzone') }}" :upload-url="getUploadUrl()" :uploaded-to="pageId" @success="uploadSuccess"></dropzone>
-                        </div>
-                        <div v-show="tab === 'link'" @keypress.enter.prevent="attachNewLink(file)">
-                            <p class="text-muted small">{{ trans('entities.attachments_explain_link') }}</p>
-                            <div class="form-group">
-                                <label for="attachment-via-link">{{ trans('entities.attachments_link_name') }}</label>
-                                <input type="text" placeholder="{{ trans('entities.attachments_link_name') }}" v-model="file.name">
-                                <p class="small text-neg" v-for="error in errors.link.name" v-text="error"></p>
-                            </div>
-                            <div class="form-group">
-                                <label for="attachment-via-link">{{ trans('entities.attachments_link_url') }}</label>
-                                <input type="text"  placeholder="{{ trans('entities.attachments_link_url_hint') }}" v-model="file.link">
-                                <p class="small text-neg" v-for="error in errors.link.link" v-text="error"></p>
-                            </div>
-                            <button @click.prevent="attachNewLink(file)" class="button primary">{{ trans('entities.attach') }}</button>
-
-                        </div>
-                    </div>
-
-                </div>
-
-                <div id="file-edit" v-if="fileToEdit" @keypress.enter.prevent="updateFile(fileToEdit)">
-                    <h5>{{ trans('entities.attachments_edit_file') }}</h5>
-
-                    <div class="form-group">
-                        <label for="attachment-name-edit">{{ trans('entities.attachments_edit_file_name') }}</label>
-                        <input type="text" id="attachment-name-edit" placeholder="{{ trans('entities.attachments_edit_file_name') }}" v-model="fileToEdit.name">
-                        <p class="small text-neg" v-for="error in errors.edit.name" v-text="error"></p>
-                    </div>
-
-                    <div class="tab-container">
-                        <div class="nav-tabs">
-                            <div @click="editTab = 'file'" :class="{selected: editTab === 'file'}" class="tab-item">{{ trans('entities.attachments_upload') }}</div>
-                            <div @click="editTab = 'link'" :class="{selected: editTab === 'link'}" class="tab-item">{{ trans('entities.attachments_set_link') }}</div>
-                        </div>
-                        <div v-if="editTab === 'file'">
-                            <dropzone :upload-url="getUploadUrl(fileToEdit)" :uploaded-to="pageId" placeholder="{{ trans('entities.attachments_edit_drop_upload') }}" @success="uploadSuccessUpdate"></dropzone>
-                            <br>
-                        </div>
-                        <div v-if="editTab === 'link'">
-                            <div class="form-group">
-                                <label for="attachment-link-edit">{{ trans('entities.attachments_link_url') }}</label>
-                                <input type="text" id="attachment-link-edit" placeholder="{{ trans('entities.attachment_link') }}" v-model="fileToEdit.link">
-                                <p class="small text-neg" v-for="error in errors.edit.link" v-text="error"></p>
-                            </div>
-                        </div>
-                    </div>
-
-                    <button type="button" class="button outline" @click="cancelEdit">{{ trans('common.back') }}</button>
-                    <button @click.enter.prevent="updateFile(fileToEdit)" class="button primary">{{ trans('common.save') }}</button>
-                </div>
-
-            </div>
-        </div>
-    @endif
-
-</div>
index 380718dd7f6a53e87a5d4636e75d54fd3d0ccd5d..ffc286c2cadadc32f3f9834dfd72ffb6be54ee3c 100644 (file)
 
             <div class="text-center px-m py-xs">
                 <div v-show="draftsEnabled" dropdown dropdown-move-menu class="dropdown-container draft-display text">
-                    <a dropdown-toggle  class="text-primary text-button"><span class="faded-text" v-text="draftText"></span>&nbsp; @icon('more')</a>
+                    <button type="button" dropdown-toggle aria-haspopup="true" aria-expanded="false" title="{{ trans('entities.pages_edit_draft_options') }}" class="text-primary text-button"><span class="faded-text" v-text="draftText"></span>&nbsp; @icon('more')</button>
                     @icon('check-circle', ['class' => 'text-pos draft-notification svg-icon', ':class' => '{visible: draftUpdated}'])
-                    <ul class="dropdown-menu">
+                    <ul class="dropdown-menu" role="menu">
                         <li>
-                            <a @click="saveDraft()" class="text-pos">@icon('save'){{ trans('entities.pages_edit_save_draft') }}</a>
+                            <button type="button" @click="saveDraft()" class="text-pos">@icon('save'){{ trans('entities.pages_edit_save_draft') }}</button>
                         </li>
                         <li v-if="isNewDraft">
                             <a href="{{ $model->getUrl('/delete') }}" class="text-neg">@icon('delete'){{ trans('entities.pages_edit_delete_draft') }}</a>
                         </li>
                         <li v-if="isUpdateDraft">
-                            <a type="button" @click="discardDraft" class="text-neg">@icon('cancel'){{ trans('entities.pages_edit_discard_draft') }}</a>
+                            <button type="button" @click="discardDraft" class="text-neg">@icon('cancel'){{ trans('entities.pages_edit_discard_draft') }}</button>
                         </li>
                     </ul>
                 </div>
@@ -44,7 +44,7 @@
 
             <div class="action-buttons px-m py-xs" v-cloak>
                 <div dropdown dropdown-move-menu class="dropdown-container">
-                    <a dropdown-toggle class="text-primary text-button">@icon('edit') <span v-text="changeSummaryShort"></span></a>
+                    <button type="button" dropdown-toggle aria-haspopup="true" aria-expanded="false" class="text-primary text-button">@icon('edit') <span v-text="changeSummaryShort"></span></button>
                     <ul class="wide dropdown-menu">
                         <li class="px-l py-m">
                             <p class="text-muted pb-s">{{ trans('entities.pages_edit_enter_changelog_desc') }}</p>
index 4650f3a1c1377808f6e09357a5c2523b37f01b13..55db85144ae1f9ba147b4da17268c78cfc2589ec 100644 (file)
@@ -15,7 +15,7 @@
             ]])
         </div>
 
-        <div class="card content-wrap">
+        <main class="card content-wrap">
             <h1 class="list-heading">{{ trans('entities.pages_new') }}</h1>
             <form action="{{  $parent->getUrl('/create-guest-page') }}" method="POST">
                 {!! csrf_field() !!}
 
                 <div class="form-group text-right">
                     <a href="{{ $parent->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
-                    <button type="submit" class="button primary">{{ trans('common.continue') }}</button>
+                    <button type="submit" class="button">{{ trans('common.continue') }}</button>
                 </div>
 
             </form>
-        </div>
+        </main>
     </div>
 
 @stop
\ No newline at end of file
index 87bde33ac913d474b55137aec76a12d053a57ad4..52644113802d419ed0e8bdafaede843dc0110fe4 100644 (file)
@@ -19,7 +19,7 @@
 
         <div markdown-input class="flex flex-fill">
                         <textarea  id="markdown-editor-input"  name="markdown" rows="5"
-                                   @if($errors->has('markdown')) class="text-neg" @endif>@if(isset($model) || old('markdown')){{htmlspecialchars( old('markdown') ? old('markdown') : ($model->markdown === '' ? $model->html : $model->markdown))}}@endif</textarea>
+                                   @if($errors->has('markdown')) class="text-neg" @endif>@if(isset($model) || old('markdown')){{ old('markdown') ? old('markdown') : ($model->markdown === '' ? $model->html : $model->markdown) }}@endif</textarea>
         </div>
 
     </div>
@@ -28,8 +28,7 @@
         <div class="editor-toolbar">
             <div class="editor-toolbar-label">{{ trans('entities.pages_md_preview') }}</div>
         </div>
-        <div class="markdown-display page-content">
-        </div>
+        <iframe srcdoc="" class="markdown-display" sandbox="allow-same-origin"></iframe>
     </div>
     <input type="hidden" name="html"/>
 
index 83421be934b4e169076285d8dc79721a7af23e51..3bf1db5e46671f98ead3e3cd8c879f0bb92887ec 100644 (file)
@@ -16,7 +16,7 @@
             ]])
         </div>
 
-        <div class="card content-wrap">
+        <main class="card content-wrap">
             <h1 class="list-heading">{{ trans('entities.pages_move') }}</h1>
 
             <form action="{{ $page->getUrl('/move') }}" method="POST">
 
                 <div class="form-group text-right">
                     <a href="{{ $page->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
-                    <button type="submit" class="button primary">{{ trans('entities.pages_move') }}</button>
+                    <button type="submit" class="button">{{ trans('entities.pages_move') }}</button>
                 </div>
             </form>
 
-        </div>
+        </main>
     </div>
 
 @stop
index 260f0e49f961eae313aa276653d982fd12ba82ad..de28137dbe02952d52f5cb563d52d95e992074f2 100644 (file)
             ]])
         </div>
 
-        <div class="card content-wrap">
+        <main class="card content-wrap">
             <h1 class="list-heading">{{ trans('entities.pages_permissions') }}</h1>
             @include('form.entity-permissions', ['model' => $page])
-        </div>
+        </main>
     </div>
 
 @stop
index d4aca5dda617e74d745ce20eea9c3856cd93f327..b4b28eaba1b1fcef7a2e56db7c37510dad40e756 100644 (file)
@@ -7,7 +7,7 @@
         </div>
         @if(userCan('page-update', $page))
             <a href="{{ $page->getUrl('/edit') }}" id="pointer-edit" data-edit-href="{{ $page->getUrl('/edit') }}"
-               class="button outline icon heading-edit-icon ml-s px-s" title="{{ trans('entities.pages_edit_content_link')}}">@icon('edit')</a>
+               class="button primary outline icon heading-edit-icon ml-s px-s" title="{{ trans('entities.pages_edit_content_link')}}">@icon('edit')</a>
         @endif
     </div>
 </div>
\ No newline at end of file
index 3ce5b349f1afc8c79645e3bcd16e500bc95e1ca3..0557b6b1cd79be45f57d51074bcbf32ab9753b86 100644 (file)
@@ -11,7 +11,7 @@
 
 @section('body')
 
-    <div class="mb-m">
+    <div class="mb-m print-hidden">
         @include('partials.breadcrumbs', ['crumbs' => [
             $page->$book,
             $page->chapter,
         ]])
     </div>
 
-    <div class="card content-wrap">
+    <main class="card content-wrap">
         <div class="page-content page-revision">
             @include('pages.page-display')
         </div>
-    </div>
+    </main>
 
 @stop
\ No newline at end of file
index f3fb048bc85ded30fe5fbd05ce2e3df452f8495e..feb3180775adaff018fe9dd739e21bc64354998c 100644 (file)
@@ -15,7 +15,7 @@
             ]])
         </div>
 
-        <div class="card content-wrap">
+        <main class="card content-wrap">
             <h1 class="list-heading">{{ trans('entities.pages_revisions') }}</h1>
             @if(count($page->revisions) > 0)
 
                                 @else
                                     <a href="{{ $revision->getUrl() }}" target="_blank">{{ trans('entities.pages_revisions_preview') }}</a>
                                     <span class="text-muted">&nbsp;|&nbsp;</span>
-                                    <a href="{{ $revision->getUrl('restore') }}"></a>
                                     <div dropdown class="dropdown-container">
-                                        <a dropdown-toggle>{{ trans('entities.pages_revisions_restore') }}</a>
-                                        <ul class="dropdown-menu">
+                                        <a dropdown-toggle href="#" aria-haspopup="true" aria-expanded="false">{{ trans('entities.pages_revisions_restore') }}</a>
+                                        <ul class="dropdown-menu" role="menu">
                                             <li class="px-m py-s"><small class="text-muted">{{trans('entities.revision_restore_confirm')}}</small></li>
                                             <li>
                                                 <form action="{{ $revision->getUrl('/restore') }}" method="POST">
@@ -66,8 +65,8 @@
                                     </div>
                                     <span class="text-muted">&nbsp;|&nbsp;</span>
                                     <div dropdown class="dropdown-container">
-                                        <a dropdown-toggle>{{ trans('common.delete') }}</a>
-                                        <ul class="dropdown-menu">
+                                        <a dropdown-toggle href="#" aria-haspopup="true" aria-expanded="false">{{ trans('common.delete') }}</a>
+                                        <ul class="dropdown-menu" role="menu">
                                             <li class="px-m py-s"><small class="text-muted">{{trans('entities.revision_delete_confirm')}}</small></li>
                                             <li>
                                                 <form action="{{ $revision->getUrl('/delete/') }}" method="POST">
@@ -87,7 +86,7 @@
             @else
                 <p>{{ trans('entities.pages_revisions_none') }}</p>
             @endif
-        </div>
+        </main>
 
     </div>
 
index fb0df2ddd027e6e6c0c880f7c58271f8f12282c7..51ab5bbbe53531e315e6ae314694123adcfc7281 100644 (file)
@@ -2,7 +2,7 @@
 
 @section('body')
 
-    <div class="mb-m">
+    <div class="mb-m print-hidden">
         @include('partials.breadcrumbs', ['crumbs' => [
             $page->book,
             $page->hasChapter() ? $page->chapter : null,
         ]])
     </div>
 
-    <div class="content-wrap card">
+    <main class="content-wrap card">
         <div class="page-content" page-display="{{ $page->id }}">
             @include('pages.pointer', ['page' => $page])
             @include('pages.page-display')
         </div>
-    </div>
+    </main>
 
     @if ($commentsEnabled)
-        <div class="container small p-none comments-container mb-l">
+        <div class="container small p-none comments-container mb-l print-hidden">
             @include('comments.comments', ['page' => $page])
             <div class="clearfix"></div>
         </div>
@@ -50,7 +50,7 @@
     @endif
 
     @if (isset($pageNav) && count($pageNav))
-        <div id="page-navigation" class="mb-xl">
+        <nav id="page-navigation" class="mb-xl" aria-label="{{ trans('entities.pages_navigation') }}">
             <h5>{{ trans('entities.pages_navigation') }}</h5>
             <div class="body">
                 <div class="sidebar-page-nav menu">
@@ -62,7 +62,7 @@
                     @endforeach
                 </div>
             </div>
-        </div>
+        </nav>
     @endif
 
     @include('partials.book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree])
                     @endif
                 </div>
             @endif
+
+            @if($page->template)
+                <div>
+                    @icon('template'){{ trans('entities.pages_is_template') }}
+                </div>
+            @endif
         </div>
     </div>
 
             <hr class="primary-background"/>
 
             {{--Export--}}
-            <div dropdown class="dropdown-container block">
-                <div dropdown-toggle class="icon-list-item">
-                    <span>@icon('export')</span>
-                    <span>{{ trans('entities.export') }}</span>
-                </div>
-                <ul class="dropdown-menu wide">
-                    <li><a href="{{ $page->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
-                    <li><a href="{{ $page->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
-                    <li><a href="{{ $page->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
-                </ul>
-            </div>
+            @include('partials.entity-export-menu', ['entity' => $page])
         </div>
 
     </div>
diff --git a/resources/views/pages/template-manager-list.blade.php b/resources/views/pages/template-manager-list.blade.php
new file mode 100644 (file)
index 0000000..f2f70c1
--- /dev/null
@@ -0,0 +1,24 @@
+{{ $templates->links() }}
+
+@foreach($templates as $template)
+    <div class="card template-item border-card p-m mb-m" tabindex="0"
+         aria-label="{{ trans('entities.templates_replace_content') }} - {{ $template->name }}"
+         draggable="true" template-id="{{ $template->id }}">
+        <div class="template-item-content" title="{{ trans('entities.templates_replace_content') }}">
+            <div>{{ $template->name }}</div>
+            <div class="text-muted">{{ trans('entities.meta_updated', ['timeLength' => $template->updated_at->diffForHumans()]) }}</div>
+        </div>
+        <div class="template-item-actions">
+            <button type="button"
+                    title="{{ trans('entities.templates_prepend_content') }}"
+                    aria-label="{{ trans('entities.templates_prepend_content') }} - {{ $template->name }}"
+                    template-action="prepend">@icon('chevron-up')</button>
+            <button type="button"
+                    title="{{ trans('entities.templates_append_content') }}"
+                    aria-label="{{ trans('entities.templates_append_content') }} -- {{ $template->name }}"
+                    template-action="append">@icon('chevron-down')</button>
+        </div>
+    </div>
+@endforeach
+
+{{ $templates->links() }}
\ No newline at end of file
diff --git a/resources/views/pages/template-manager.blade.php b/resources/views/pages/template-manager.blade.php
new file mode 100644 (file)
index 0000000..fbdb70a
--- /dev/null
@@ -0,0 +1,25 @@
+<div template-manager>
+    @if(userCan('templates-manage'))
+        <p class="text-muted small mb-none">
+            {{ trans('entities.templates_explain_set_as_template') }}
+        </p>
+        @include('components.toggle-switch', [
+               'name' => 'template',
+               'value' => old('template', $page->template ? 'true' : 'false') === 'true',
+               'label' => trans('entities.templates_set_as_template')
+        ])
+        <hr>
+    @endif
+
+    @if(count($templates) > 0)
+        <div class="search-box flexible mb-m">
+            <input type="text" name="template-search" placeholder="{{ trans('common.search') }}">
+            <button type="button">@icon('search')</button>
+            <button class="search-box-cancel text-neg hidden" type="button">@icon('close')</button>
+        </div>
+    @endif
+
+    <div template-manager-list>
+        @include('pages.template-manager-list', ['templates' => $templates])
+    </div>
+</div>
\ No newline at end of file
index f9a0f03cf3d3e2fda35e6902133e9b39a7473bf5..1a67ee76f2d0992f7eb552878359f6d151db3b51 100644 (file)
@@ -5,7 +5,7 @@
     ])
 
     <textarea id="html-editor"  name="html" rows="5" v-pre
-          @if($errors->has('html')) class="text-neg" @endif>@if(isset($model) || old('html')){{htmlspecialchars( old('html') ? old('html') : $model->html)}}@endif</textarea>
+          @if($errors->has('html')) class="text-neg" @endif>@if(isset($model) || old('html')){{ old('html') ? old('html') : $model->html }}@endif</textarea>
 </div>
 
 @if($errors->has('html'))
index 73064dceb46b72b6d9f9f38b23c9aeb5e75d55af..c288e63674ff38b456a4b13ed530746510f11e29 100644 (file)
@@ -1,4 +1,4 @@
-<div id="book-tree" class="book-tree mb-xl" v-pre>
+<nav id="book-tree" class="book-tree mb-xl" v-pre aria-label="{{ trans('entities.books_navigation') }}">
     <h5>{{ trans('entities.books_navigation') }}</h5>
 
     <ul class="sidebar-page-list mt-xs menu entity-list">
 
                 @if($bookChild->isA('chapter') && count($bookChild->pages) > 0)
                     <div class="entity-list-item no-hover">
-                        <span class="icon text-chapter">
-
-                        </span>
+                        <span role="presentation" class="icon text-chapter"></span>
                         <div class="content">
-                            @include('chapters.child-menu', ['chapter' => $bookChild, 'current' => $current])
+                            @include('chapters.child-menu', [
+                                'chapter' => $bookChild,
+                                'current' => $current,
+                                'isOpen'  => $bookChild->matchesOrContains($current)
+                            ])
                         </div>
                     </div>
 
@@ -27,4 +29,4 @@
             </li>
         @endforeach
     </ul>
-</div>
\ No newline at end of file
+</nav>
\ No newline at end of file
index 3dea3202355b6d5953cc7d48bb51fc71269555d4..e53cb4c53b9cf59200427f67d6134d90ce1c5bbd 100644 (file)
@@ -1,11 +1,12 @@
 <div class="breadcrumb-listing" dropdown breadcrumb-listing="{{ $entity->getType() }}:{{ $entity->id }}">
-    <div class="breadcrumb-listing-toggle" dropdown-toggle>
+    <div class="breadcrumb-listing-toggle" dropdown-toggle
+         aria-haspopup="true" aria-expanded="false" tabindex="0">
         <div class="separator">@icon('chevron-right')</div>
     </div>
-    <div dropdown-menu class="breadcrumb-listing-dropdown card">
+    <div dropdown-menu class="breadcrumb-listing-dropdown card" role="menu">
         <div class="breadcrumb-listing-search">
             @icon('search')
-            <input autocomplete="off" type="text" name="entity-search">
+            <input autocomplete="off" type="text" name="entity-search" placeholder="{{ trans('common.search') }}" aria-label="{{ trans('common.search') }}">
         </div>
         @include('partials.loading-icon')
         <div class="breadcrumb-listing-entity-list px-m"></div>
index 28c7196ee490c9e0e8a0039f984e200b00e128d6..58ccd51257e0338686e746762781d36b318b5d70 100644 (file)
@@ -1,8 +1,8 @@
-<div class="breadcrumbs text-center">
+<nav class="breadcrumbs text-center" aria-label="{{ trans('common.breadcrumb') }}">
     <?php $breadcrumbCount = 0; ?>
 
     {{-- Show top level books item --}}
-    @if (count($crumbs) > 0 && array_first($crumbs) instanceof  \BookStack\Entities\Book)
+    @if (count($crumbs) > 0 && ($crumbs[0] ?? null) instanceof  \BookStack\Entities\Book)
         <a href="{{  url('/books')  }}" class="text-book icon-list-item outline-hover">
             <span>@icon('books')</span>
             <span>{{ trans('entities.books') }}</span>
@@ -11,7 +11,7 @@
     @endif
 
     {{-- Show top level shelves item --}}
-    @if (count($crumbs) > 0 && array_first($crumbs) instanceof  \BookStack\Entities\Bookshelf)
+    @if (count($crumbs) > 0 && ($crumbs[0] ?? null) instanceof  \BookStack\Entities\Bookshelf)
         <a href="{{  url('/shelves')  }}" class="text-bookshelf icon-list-item outline-hover">
             <span>@icon('bookshelf')</span>
             <span>{{ trans('entities.shelves') }}</span>
@@ -51,4 +51,4 @@
         @endif
         <?php $breadcrumbCount++; ?>
     @endforeach
-</div>
\ No newline at end of file
+</nav>
\ No newline at end of file
index 2a293edc5f4b5f22209d13860e038508978eee67..9080790825f74c4014f53368458f7c3fe3705570 100644 (file)
@@ -1,24 +1,6 @@
 <style id="custom-styles" data-color="{{ setting('app-color') }}" data-color-light="{{ setting('app-color-light') }}">
-    .primary-background {
-        background-color: {{ setting('app-color') }} !important;
+    :root {
+        --color-primary: {{ setting('app-color') }};
+        --color-primary-light: {{ setting('app-color-light') }};
     }
-    .primary-background-light {
-        background-color: {{ setting('app-color-light') }};
-    }
-    .button.primary, .button.primary:hover, .button.primary:active, .button.primary:focus {
-        background-color: {{ setting('app-color') }};
-        border-color: {{ setting('app-color') }};
-    }
-    .nav-tabs a.selected, .nav-tabs .tab-item.selected {
-        border-bottom-color: {{ setting('app-color') }};
-    }
-    .text-primary, .text-primary-hover:hover, .text-primary:hover {
-        color: {{ setting('app-color') }} !important;
-        fill: {{ setting('app-color') }} !important;
-    }
-
-    a, a:hover, a:focus, .text-button, .text-button:hover, .text-button:focus {
-        color: {{ setting('app-color') }};
-        fill: {{ setting('app-color') }};
-    }
-</style>
+</style>
\ No newline at end of file
index 99d37c5f874bc390de36fbb595e5c9d750379b67..2e0395253b7680e5b21166228c59e8389118d4ce 100644 (file)
@@ -1,7 +1,8 @@
 <div class="mb-xl">
-    <form v-on:submit.prevent="searchBook" class="search-box flexible">
-        <input v-model="searchTerm" v-on:change="checkSearchForm" type="text" name="term" placeholder="{{ trans('entities.books_search_this') }}">
-        <button type="submit">@icon('search')</button>
-        <button v-if="searching" v-cloak class="search-box-cancel text-neg" v-on:click="clearSearch" type="button">@icon('close')</button>
+    <form v-on:submit.prevent="searchBook" class="search-box flexible" role="search">
+        <input v-model="searchTerm" v-on:change="checkSearchForm" type="text" aria-label="{{ trans('entities.books_search_this') }}" name="term" placeholder="{{ trans('entities.books_search_this') }}">
+        <button type="submit" aria-label="{{ trans('common.search') }}">@icon('search')</button>
+        <button v-if="searching" v-cloak class="search-box-cancel text-neg" v-on:click="clearSearch"
+                type="button" aria-label="{{ trans('common.search_clear') }}">@icon('close')</button>
     </form>
 </div>
\ No newline at end of file
diff --git a/resources/views/partials/entity-export-menu.blade.php b/resources/views/partials/entity-export-menu.blade.php
new file mode 100644 (file)
index 0000000..630d640
--- /dev/null
@@ -0,0 +1,12 @@
+<div dropdown class="dropdown-container" id="export-menu">
+    <div dropdown-toggle class="icon-list-item"
+         aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('entities.export') }}" tabindex="0">
+        <span>@icon('export')</span>
+        <span>{{ trans('entities.export') }}</span>
+    </div>
+    <ul class="wide dropdown-menu" role="menu">
+        <li><a href="{{ $entity->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
+        <li><a href="{{ $entity->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
+        <li><a href="{{ $entity->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
+    </ul>
+</div>
\ No newline at end of file
index c4942c71f2dd38af6bb2de09290a710bd08b7912..2ec4bee5cc07dbf13db6b0978effd5990551cdc4 100644 (file)
@@ -1,6 +1,6 @@
 <?php $type = $entity->getType(); ?>
 <a href="{{ $entity->getUrl() }}" class="{{$type}} {{$type === 'page' && $entity->draft ? 'draft' : ''}} {{$classes ?? ''}} entity-list-item" data-entity-type="{{$type}}" data-entity-id="{{$entity->id}}">
-    <span class="icon text-{{$type}}">@icon($type)</span>
+    <span role="presentation" class="icon text-{{$type}}">@icon($type)</span>
     <div class="content">
             <h4 class="entity-list-item-name break-text">{{ $entity->name }}</h4>
             {{ $slot ?? '' }}
index ac853a56cc01b79f7a1c7a681a9e0ec59da33158..52687149928b299777276912b6e1870c4d9c90e7 100644 (file)
@@ -1,11 +1,11 @@
-<div notification="success" style="display: none;" data-autohide class="pos" @if(session()->has('success')) data-show @endif>
+<div notification="success" style="display: none;" data-autohide class="pos" role="alert" @if(session()->has('success')) data-show @endif>
     @icon('check-circle') <span>{!! nl2br(htmlentities(session()->get('success'))) !!}</span>
 </div>
 
-<div notification="warning" style="display: none;" class="warning" @if(session()->has('warning')) data-show @endif>
+<div notification="warning" style="display: none;" class="warning" role="alert" @if(session()->has('warning')) data-show @endif>
     @icon('info') <span>{!! nl2br(htmlentities(session()->get('warning'))) !!}</span>
 </div>
 
-<div notification="error" style="display: none;" class="neg" @if(session()->has('error')) data-show @endif>
+<div notification="error" style="display: none;" class="neg" role="alert" @if(session()->has('error')) data-show @endif>
     @icon('danger') <span>{!! nl2br(htmlentities(session()->get('error'))) !!}</span>
 </div>
index 38145df219f4c9d7c70d7a2a5d47f4273b12c287..09c61d01383fa01f2d5e6f0c1af54b586dff75c7 100644 (file)
 
         <div class="list-sort">
             <div class="list-sort-type dropdown-container" dropdown>
-                <div dropdown-toggle>{{ $options[$selectedSort] }}</div>
+                <div dropdown-toggle aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" tabindex="0">{{ $options[$selectedSort] }}</div>
                 <ul class="dropdown-menu">
                     @foreach($options as $key => $label)
                         <li @if($key === $selectedSort) class="active" @endif><a href="#" data-sort-value="{{$key}}">{{ $label }}</a></li>
                     @endforeach
                 </ul>
             </div>
-            <div class="list-sort-dir" data-sort-dir>
+            <button href="#" class="list-sort-dir" type="button" data-sort-dir
+                    aria-label="{{ trans('common.sort_direction_toggle') }} - {{ $order === 'asc' ? trans('common.sort_ascending') : trans('common.sort_descending') }}" tabindex="0">
                 @icon($order === 'desc' ? 'sort-up' : 'sort-down')
-            </div>
+            </button>
         </div>
     </form>
 </div>
\ No newline at end of file
index 7a2cf65bd35694f39ecdf8c60e9dce3905cd4901..f19e560a2d5f633b01bf063c074d5396a5819419 100644 (file)
                         </table>
 
 
-                        <button type="submit" class="button primary">{{ trans('entities.search_update') }}</button>
+                        <button type="submit" class="button">{{ trans('entities.search_update') }}</button>
                     </form>
 
                 </div>
index 510e3af1bb662f3a95f966a9d7d886684bbb724c..a5277418661ea29af619b3f9fb58a0abadd13de8 100644 (file)
@@ -7,9 +7,10 @@
             <div class="py-m">
                 @include('settings.navbar', ['selected' => 'settings'])
             </div>
-            <div class="text-right mb-l px-m">
-                <br>
-                BookStack @if(strpos($version, 'v') !== 0) version @endif {{ $version }}
+            <div class="text-right p-m">
+                <a target="_blank" rel="noopener noreferrer" href="https://github.com/BookStackApp/BookStack/releases">
+                    BookStack @if(strpos($version, 'v') !== 0) version @endif {{ $version }}
+                </a>
             </div>
         </div>
 
@@ -72,7 +73,7 @@
                 </div>
 
                 <div class="form-group text-right">
-                    <button type="submit" class="button primary">{{ trans('settings.settings_save') }}</button>
+                    <button type="submit" class="button">{{ trans('settings.settings_save') }}</button>
                 </div>
             </form>
         </div>
                             <p class="small">{!! trans('settings.app_primary_color_desc') !!}</p>
                         </div>
                         <div setting-app-color-picker class="text-m-right">
-                            <input type="color" value="{{ setting('app-color') }}" name="setting-app-color" id="setting-app-color" placeholder="#0288D1">
+                            <input type="color" value="{{ setting('app-color') }}" name="setting-app-color" id="setting-app-color" placeholder="#206ea7">
                             <input type="hidden" value="{{ setting('app-color-light') }}" name="setting-app-color-light" id="setting-app-color-light">
                             <br>
                             <button type="button" class="text-button text-muted mt-s mx-s" setting-app-color-picker-reset>{{ trans('common.reset') }}</button>
                 </div>
 
                 <div class="form-group text-right">
-                    <button type="submit" class="button primary">{{ trans('settings.settings_save') }}</button>
+                    <button type="submit" class="button">{{ trans('settings.settings_save') }}</button>
                 </div>
             </form>
         </div>
                 </div>
 
                 <div class="form-group text-right">
-                    <button type="submit" class="button primary">{{ trans('settings.settings_save') }}</button>
+                    <button type="submit" class="button">{{ trans('settings.settings_save') }}</button>
                 </div>
             </form>
         </div>
index 6be49cdf2c94479d077ef2258f36bcdd08d1183c..7311bbbe20c96ab7ea15fe5d48ed817985a358e8 100644 (file)
@@ -7,13 +7,13 @@
         <div class="py-m">
             @include('settings.navbar', ['selected' => 'maintenance'])
         </div>
-        <div class="text-right mb-l px-m">
-            <br>
+        <div class="text-right p-m">
+            <a target="_blank" rel="noopener noreferrer" href="https://github.com/BookStackApp/BookStack/releases">
             BookStack @if(strpos($version, 'v') !== 0) version @endif {{ $version }}
+            </a>
         </div>
     </div>
 
-
     <div id="image-cleanup" class="card content-wrap auto-height">
         <h2 class="list-heading">{{ trans('settings.maint_image_cleanup') }}</h2>
         <div class="grid half gap-xl">
         </div>
     </div>
 
+    <div id="send-test-email" class="card content-wrap auto-height">
+        <h2 class="list-heading">{{ trans('settings.maint_send_test_email') }}</h2>
+        <div class="grid half gap-xl">
+            <div>
+                <p class="small text-muted">{{ trans('settings.maint_send_test_email_desc') }}</p>
+            </div>
+            <div>
+                <form method="POST" action="{{ url('/settings/maintenance/send-test-email') }}">
+                    {!! csrf_field()  !!}
+                    <button class="button outline">{{ trans('settings.maint_send_test_email_run') }}</button>
+                </form>
+            </div>
+        </div>
+    </div>
+
 </div>
 @stop
index 51fda5b9031e57f967e895b4b7ab6761f0e65fd4..896de9d97477c0c3e510ca806ed7e33cbf12e611 100644 (file)
@@ -1,5 +1,5 @@
 
-<div class="active-link-list">
+<nav class="active-link-list">
     @if($currentUser->can('settings-manage'))
         <a href="{{ url('/settings') }}" @if($selected == 'settings') class="active" @endif>@icon('settings'){{ trans('settings.settings') }}</a>
         <a href="{{ url('/settings/maintenance') }}" @if($selected == 'maintenance') class="active" @endif>@icon('spanner'){{ trans('settings.maint') }}</a>
@@ -10,4 +10,4 @@
     @if($currentUser->can('user-roles-manage'))
         <a href="{{ url('/settings/roles') }}" @if($selected == 'roles') class="active" @endif>@icon('lock-open'){{ trans('settings.roles') }}</a>
     @endif
-</div>
\ No newline at end of file
+</nav>
\ No newline at end of file
index e0075fa8ad27d5a4811ec6aa89ac7c25b56ee113..4f40345df99947dec5de11fa2d44cf88dcf382e0 100644 (file)
@@ -32,7 +32,7 @@
                     <div>
                         <div class="form-group text-right">
                             <a href="{{ url("/settings/roles/{$role->id}") }}" class="button outline">{{ trans('common.cancel') }}</a>
-                            <button type="submit" class="button primary">{{ trans('common.confirm') }}</button>
+                            <button type="submit" class="button">{{ trans('common.confirm') }}</button>
                         </div>
                     </div>
                 </div>
index d7c1fc47ce826dff36ee09b4112d00d9c5c4315f..94e63b158243bc8b2524dd3e838844734dcc79d1 100644 (file)
@@ -38,6 +38,7 @@
                 <div>@include('settings.roles.checkbox', ['permission' => 'user-roles-manage', 'label' => trans('settings.role_manage_roles')])</div>
                 <div>@include('settings.roles.checkbox', ['permission' => 'restrictions-manage-all', 'label' => trans('settings.role_manage_entity_permissions')])</div>
                 <div>@include('settings.roles.checkbox', ['permission' => 'restrictions-manage-own', 'label' => trans('settings.role_manage_own_entity_permissions')])</div>
+                <div>@include('settings.roles.checkbox', ['permission' => 'templates-manage', 'label' => trans('settings.role_manage_page_templates')])</div>
                 <div>@include('settings.roles.checkbox', ['permission' => 'settings-manage', 'label' => trans('settings.role_manage_settings')])</div>
             </div>
         </div>
         @if (isset($role) && $role->id)
             <a href="{{ url("/settings/roles/delete/{$role->id}") }}" class="button outline">{{ trans('settings.role_delete') }}</a>
         @endif
-        <button type="submit" class="button primary">{{ trans('settings.role_save') }}</button>
+        <button type="submit" class="button">{{ trans('settings.role_save') }}</button>
     </div>
 
 </div>
index aee1c5a4299bfe6352dfec6d550c6f982abcde9a..bea20eca93624cf234e89b73099b51fc5574c3c4 100644 (file)
             ]])
         </div>
 
-        <div class="card content-wrap">
+        <main class="card content-wrap">
             <h1 class="list-heading">{{ trans('entities.shelves_create') }}</h1>
             <form action="{{ url("/shelves") }}" method="POST" enctype="multipart/form-data">
                 @include('shelves.form', ['shelf' => null, 'books' => $books])
             </form>
-        </div>
+        </main>
 
     </div>
 
index 8c2cd4f45e7d11d816a3472a9e0b109c0aeb392d..5ae3638fee955ec3f30ee3c068f5c45e6f7b7976 100644 (file)
             ]])
         </div>
 
-        <div class="card content-wrap">
+        <main class="card content-wrap">
             <h1 class="list-heading">{{ trans('entities.shelves_edit') }}</h1>
             <form action="{{ $shelf->getUrl() }}" method="POST" enctype="multipart/form-data">
                 <input type="hidden" name="_method" value="PUT">
                 @include('shelves.form', ['model' => $shelf])
             </form>
-        </div>
+        </main>
     </div>
 
 @stop
\ No newline at end of file
index 1d152a143459aa411ad6535213ff41b371c6e199..19c5bbecd69ae95c70bd782703cfe377ddd32b30 100644 (file)
     <div class="form-group">
         <label for="books">{{ trans('entities.shelves_books') }}</label>
         <input type="hidden" id="books-input" name="books"
-               value="{{ isset($shelf) ? $shelf->books->implode('id', ',') : '' }}">
+               value="{{ isset($shelf) ? $shelf->visibleBooks->implode('id', ',') : '' }}">
         <div class="scroll-box" shelf-sort-assigned-books data-instruction="{{ trans('entities.shelves_drag_books') }}">
-            @if (isset($shelfBooks) && count($shelfBooks) > 0)
-                @foreach ($shelfBooks as $book)
+            @if (count($shelf->visibleBooks ?? []) > 0)
+                @foreach ($shelf->visibleBooks as $book)
                     <div data-id="{{ $book->id }}" class="scroll-box-item">
                         <a href="{{ $book->getUrl() }}" class="text-book">@icon('book'){{ $book->name }}</a>
                     </div>
@@ -40,9 +40,9 @@
 
 
 <div class="form-group" collapsible id="logo-control">
-    <div class="collapse-title text-primary" collapsible-trigger>
-        <label for="user-avatar">{{ trans('common.cover_image') }}</label>
-    </div>
+    <button type="button" class="collapse-title text-primary" collapsible-trigger aria-expanded="false">
+        <label>{{ trans('common.cover_image') }}</label>
+    </button>
     <div class="collapse-content" collapsible-content>
         <p class="small">{{ trans('common.cover_image_description') }}</p>
 
@@ -56,9 +56,9 @@
 </div>
 
 <div class="form-group" collapsible id="tags-control">
-    <div class="collapse-title text-primary" collapsible-trigger>
+    <button type="button" class="collapse-title text-primary" collapsible-trigger aria-expanded="false">
         <label for="tag-manager">{{ trans('entities.shelf_tags') }}</label>
-    </div>
+    </button>
     <div class="collapse-content" collapsible-content>
         @include('components.tag-manager', ['entity' => $shelf ?? null, 'entityType' => 'bookshelf'])
     </div>
@@ -66,5 +66,5 @@
 
 <div class="form-group text-right">
     <a href="{{ isset($shelf) ? $shelf->getUrl() : url('/shelves') }}" class="button outline">{{ trans('common.cancel') }}</a>
-    <button type="submit" class="button primary">{{ trans('entities.shelves_save') }}</button>
+    <button type="submit" class="button">{{ trans('entities.shelves_save') }}</button>
 </div>
\ No newline at end of file
index 3f8c266e992b83e23cd261d3860a0d0e6973541e..b20b08a2c59e40f8a110394404f63ac198c91f8f 100644 (file)
@@ -1,5 +1,5 @@
 
-<div class="content-wrap mt-m card">
+<main class="content-wrap mt-m card">
 
     <div class="grid half v-center">
         <h1 class="list-heading">{{ trans('entities.shelves') }}</h1>
@@ -35,4 +35,4 @@
         @endif
     @endif
 
-</div>
+</main>
index 3a9d599519a95035e7d4a8cb8dfbcca23303669f..2212e1c1e32ae3014b3f839f802d1dc534542c3a 100644 (file)
@@ -8,13 +8,13 @@
         ]])
     </div>
 
-    <div class="card content-wrap">
+    <main class="card content-wrap">
         <h1 class="break-text">{{$shelf->name}}</h1>
         <div class="book-content">
             <p class="text-muted">{!! nl2br(e($shelf->description)) !!}</p>
-            @if(count($books) > 0)
+            @if(count($shelf->visibleBooks) > 0)
                 <div class="entity-list">
-                    @foreach($books as $book)
+                    @foreach($shelf->visibleBooks as $book)
                         @include('books.list-item', ['book' => $book])
                     @endforeach
                 </div>
@@ -39,7 +39,7 @@
                 </div>
             @endif
         </div>
-    </div>
+    </main>
 
 @stop
 
index 00e9df2f970fbf9828bb11793eb3bbc3d64d4e17..71c546964ef6fb31d89a553256654324998de35b 100644 (file)
@@ -4,7 +4,7 @@
 
 @section('content')
 
-    <div class="tri-layout-mobile-tabs text-primary>
+    <div class="tri-layout-mobile-tabs text-primary print-hidden">
         <div class="grid half no-break no-gap">
             <div class="tri-layout-mobile-tab px-m py-s" tri-layout-mobile-tab="info">
                 {{ trans('common.tab_info') }}
@@ -18,9 +18,9 @@
     <div class="tri-layout-container" tri-layout @yield('container-attrs') >
 
         <div class="tri-layout-left print-hidden pt-m" id="sidebar">
-            <div class="tri-layout-left-contents">
+            <aside class="tri-layout-left-contents">
                 @yield('left')
-            </div>
+            </aside>
         </div>
 
         <div class="@yield('body-wrap-classes') tri-layout-middle">
@@ -30,9 +30,9 @@
         </div>
 
         <div class="tri-layout-right print-hidden pt-m">
-            <div class="tri-layout-right-contents">
+            <aside class="tri-layout-right-contents">
                 @yield('right')
-            </div>
+            </aside>
         </div>
     </div>
 
index b9f404bb712c9d0674f36ee5ebe51528b572d393..9971eeeeb54ca63ba42045982f076e6324936989 100644 (file)
@@ -8,7 +8,7 @@
             @include('settings.navbar', ['selected' => 'users'])
         </div>
 
-        <div class="card content-wrap">
+        <main class="card content-wrap">
             <h1 class="list-heading">{{ trans('settings.users_add_new') }}</h1>
 
             <form action="{{ url("/settings/users/create") }}" method="post">
 
                 <div class="form-group text-right">
                     <a href="{{  url($currentUser->can('users-manage') ? "/settings/users" : "/") }}" class="button outline">{{ trans('common.cancel') }}</a>
-                    <button class="button primary" type="submit">{{ trans('common.save') }}</button>
+                    <button class="button" type="submit">{{ trans('common.save') }}</button>
                 </div>
 
             </form>
 
-        </div>
+        </main>
     </div>
 
 @stop
index aa9811bf5fb190013cf20da1facacae8a9ad3373..d3349c2f3fc29b93e6b10319e95d0cefc97df7e5 100644 (file)
@@ -20,7 +20,7 @@
 
                         <input type="hidden" name="_method" value="DELETE">
                         <a href="{{ url("/settings/users/{$user->id}") }}" class="button outline">{{ trans('common.cancel') }}</a>
-                        <button type="submit" class="button primary">{{ trans('common.confirm') }}</button>
+                        <button type="submit" class="button">{{ trans('common.confirm') }}</button>
                     </form>
                 </div>
             </div>
index 92a36c943aa1d25e2d12f4147e686179010ce29c..ff1e7cbe5d51577e2cd807dc4842aee8b268f1c8 100644 (file)
@@ -7,7 +7,7 @@
             @include('settings.navbar', ['selected' => 'users'])
         </div>
 
-        <div class="card content-wrap">
+        <section class="card content-wrap">
             <h1 class="list-heading">{{ $user->id === $currentUser->id ? trans('settings.users_edit_profile') : trans('settings.users_edit') }}</h1>
             <form action="{{ url("/settings/users/{$user->id}") }}" method="post" enctype="multipart/form-data">
                 {!! csrf_field() !!}
                     @if($authMethod !== 'system')
                         <a href="{{ url("/settings/users/{$user->id}/delete") }}" class="button outline">{{ trans('settings.users_delete') }}</a>
                     @endif
-                    <button class="button primary" type="submit">{{ trans('common.save') }}</button>
+                    <button class="button" type="submit">{{ trans('common.save') }}</button>
                 </div>
             </form>
-        </div>
+        </section>
 
         @if($currentUser->id === $user->id && count($activeSocialDrivers) > 0)
-            <div class="card content-wrap auto-height">
+            <section class="card content-wrap auto-height">
                 <h2 class="list-heading">{{ trans('settings.users_social_accounts') }}</h2>
                 <p class="text-muted">{{ trans('settings.users_social_accounts_info') }}</p>
                 <div class="container">
                     <div class="grid third">
                         @foreach($activeSocialDrivers as $driver => $enabled)
                             <div class="text-center mb-m">
-                                <div>@icon('auth/'. $driver, ['style' => 'width: 56px;height: 56px;'])</div>
+                                <div role="presentation">@icon('auth/'. $driver, ['style' => 'width: 56px;height: 56px;'])</div>
                                 <div>
                                     @if($user->hasSocialAccount($driver))
-                                        <a href="{{ url("/login/service/{$driver}/detach") }}" class="button small outline">{{ trans('settings.users_social_disconnect') }}</a>
+                                        <a href="{{ url("/login/service/{$driver}/detach") }}" aria-label="{{ trans('settings.users_social_disconnect') }} - {{ $driver }}"
+                                           class="button small outline">{{ trans('settings.users_social_disconnect') }}</a>
                                     @else
-                                        <a href="{{ url("/login/service/{$driver}") }}" class="button small outline">{{ trans('settings.users_social_connect') }}</a>
+                                        <a href="{{ url("/login/service/{$driver}") }}" aria-label="{{ trans('settings.users_social_connect') }} - {{ $driver }}"
+                                           class="button small outline">{{ trans('settings.users_social_connect') }}</a>
                                     @endif
                                 </div>
                             </div>
                         @endforeach
                     </div>
                 </div>
-            </div>
+            </section>
         @endif
     </div>
 
index 7a3d44935ba8ec16e9f2bfa41d4e3b53b2a95df9..6c08cad44f12a85c87f4f9b695c47b710b4cddd7 100644 (file)
@@ -19,7 +19,7 @@
         <div>
             @if($authMethod !== 'ldap' || userCan('users-manage'))
                 <label for="email">{{ trans('auth.email') }}</label>
-                @include('form.text', ['name' => 'email'])
+                @include('form.text', ['name' => 'email', 'disabled' => !userCan('users-manage')])
             @endif
         </div>
     </div>
 @endif
 
 @if($authMethod === 'standard')
-    <div>
+    <div new-user-password>
         <label class="setting-list-label">{{ trans('settings.users_password') }}</label>
-        <p class="small">{{ trans('settings.users_password_desc') }}</p>
-        @if(isset($model))
+
+        @if(!isset($model))
             <p class="small">
-                {{ trans('settings.users_password_warning') }}
+                {{ trans('settings.users_send_invite_text') }}
             </p>
+
+            @include('components.toggle-switch', [
+                'name' => 'send_invite',
+                'value' => old('send_invite', 'true') === 'true',
+                'label' => trans('settings.users_send_invite_option')
+            ])
+
         @endif
-        <div class="grid half mt-m gap-xl">
-            <div>
-                <label for="password">{{ trans('auth.password') }}</label>
-                @include('form.password', ['name' => 'password'])
-            </div>
-            <div>
-                <label for="password-confirm">{{ trans('auth.password_confirm') }}</label>
-                @include('form.password', ['name' => 'password-confirm'])
+
+        <div id="password-input-container" @if(!isset($model)) style="display: none;" @endif>
+            <p class="small">{{ trans('settings.users_password_desc') }}</p>
+            @if(isset($model))
+                <p class="small">
+                    {{ trans('settings.users_password_warning') }}
+                </p>
+            @endif
+            <div class="grid half mt-m gap-xl">
+                <div>
+                    <label for="password">{{ trans('auth.password') }}</label>
+                    @include('form.password', ['name' => 'password'])
+                </div>
+                <div>
+                    <label for="password-confirm">{{ trans('auth.password_confirm') }}</label>
+                    @include('form.password', ['name' => 'password-confirm'])
+                </div>
             </div>
         </div>
+
     </div>
 @endif
index 72db240758f228c7b56d045125b2b01140ec0323..da373c1618b563fddcc9768644b7d1ac608e29cf 100644 (file)
@@ -7,7 +7,7 @@
             @include('settings.navbar', ['selected' => 'users'])
         </div>
 
-        <div class="card content-wrap">
+        <main class="card content-wrap">
 
             <div class="grid right-focus v-center">
                 <h1 class="list-heading">{{ trans('settings.users') }}</h1>
@@ -62,7 +62,7 @@
             <div>
                 {{ $users->links() }}
             </div>
-        </div>
+        </main>
 
     </div>
 
index f817e328f410ae6358314b17dc9903ec23bebfd1..4028b5c1da731c45925d8d27caea334b46cc0e74 100644 (file)
@@ -7,14 +7,14 @@
         <div class="grid right-focus reverse-collapse">
 
             <div>
-                <div id="recent-user-activity" class="mb-xl">
+                <section id="recent-user-activity" class="mb-xl">
                     <h5>{{ trans('entities.recent_activity') }}</h5>
                     @include('partials.activity-list', ['activity' => $activity])
-                </div>
+                </section>
             </div>
 
             <div>
-                <div class="card content-wrap auto-height">
+                <section class="card content-wrap auto-height">
                     <div class="grid half v-center">
                         <div>
                             <div class="mr-m float left">
@@ -54,9 +54,9 @@
 
                         </div>
                     </div>
-                </div>
+                </section>
 
-                <div class="card content-wrap auto-height book-contents">
+                <section class="card content-wrap auto-height book-contents">
                     <h2 id="recent-pages" class="list-heading">
                         {{ trans('entities.recently_created_pages') }}
                         @if (count($recentlyCreated['pages']) > 0)
@@ -68,9 +68,9 @@
                     @else
                         <p class="text-muted">{{ trans('entities.profile_not_created_pages', ['userName' => $user->name]) }}</p>
                     @endif
-                </div>
+                </section>
 
-                <div class="card content-wrap auto-height book-contents">
+                <section class="card content-wrap auto-height book-contents">
                     <h2 id="recent-chapters" class="list-heading">
                         {{ trans('entities.recently_created_chapters') }}
                         @if (count($recentlyCreated['chapters']) > 0)
@@ -82,9 +82,9 @@
                     @else
                         <p class="text-muted">{{ trans('entities.profile_not_created_chapters', ['userName' => $user->name]) }}</p>
                     @endif
-                </div>
+                </section>
 
-                <div class="card content-wrap auto-height book-contents">
+                <section class="card content-wrap auto-height book-contents">
                     <h2 id="recent-books" class="list-heading">
                         {{ trans('entities.recently_created_books') }}
                         @if (count($recentlyCreated['books']) > 0)
@@ -96,9 +96,9 @@
                     @else
                         <p class="text-muted">{{ trans('entities.profile_not_created_books', ['userName' => $user->name]) }}</p>
                     @endif
-                </div>
+                </section>
 
-                <div class="card content-wrap auto-height book-contents">
+                <section class="card content-wrap auto-height book-contents">
                     <h2 id="recent-shelves" class="list-heading">
                         {{ trans('entities.recently_created_shelves') }}
                         @if (count($recentlyCreated['shelves']) > 0)
                     @else
                         <p class="text-muted">{{ trans('entities.profile_not_created_shelves', ['userName' => $user->name]) }}</p>
                     @endif
-                </div>
+                </section>
             </div>
 
         </div>
 
 
     </div>
-
-
 @stop
\ No newline at end of file
index b8a93643ae0424538c88a60b4b27326c345631e5..f73b87b597853ccf5becce9eacbf5c4c7db68048 100644 (file)
@@ -1,5 +1,5 @@
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html>
+<html lang="{{ config('app.lang') }}">
 <head>
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
index 94dd576fe2e928a0229123013e14b824d9b14bdb..eafb6a45cbc6f91d1881f453439d53902437d218 100644 (file)
@@ -9,9 +9,7 @@ Route::group(['middleware' => 'auth'], function () {
     Route::get('/uploads/images/{path}', 'Images\ImageController@showImage')
         ->where('path', '.*$');
 
-    Route::group(['prefix' => 'pages'], function() {
-        Route::get('/recently-updated', 'PageController@showRecentlyUpdated');
-    });
+    Route::get('/pages/recently-updated', 'PageController@showRecentlyUpdated');
 
     // Shelves
     Route::get('/create-shelf', 'BookshelfController@create');
@@ -40,16 +38,16 @@ Route::group(['middleware' => 'auth'], function () {
         Route::get('/{slug}/edit', 'BookController@edit');
         Route::put('/{slug}', 'BookController@update');
         Route::delete('/{id}', 'BookController@destroy');
-        Route::get('/{slug}/sort-item', 'BookController@getSortItem');
+        Route::get('/{slug}/sort-item', 'BookSortController@showItem');
         Route::get('/{slug}', 'BookController@show');
         Route::get('/{bookSlug}/permissions', 'BookController@showPermissions');
         Route::put('/{bookSlug}/permissions', 'BookController@permissions');
         Route::get('/{slug}/delete', 'BookController@showDelete');
-        Route::get('/{bookSlug}/sort', 'BookController@sort');
-        Route::put('/{bookSlug}/sort', 'BookController@saveSort');
-        Route::get('/{bookSlug}/export/html', 'BookController@exportHtml');
-        Route::get('/{bookSlug}/export/pdf', 'BookController@exportPdf');
-        Route::get('/{bookSlug}/export/plaintext', 'BookController@exportPlainText');
+        Route::get('/{bookSlug}/sort', 'BookSortController@show');
+        Route::put('/{bookSlug}/sort', 'BookSortController@update');
+        Route::get('/{bookSlug}/export/html', 'BookExportController@html');
+        Route::get('/{bookSlug}/export/pdf', 'BookExportController@pdf');
+        Route::get('/{bookSlug}/export/plaintext', 'BookExportController@plainText');
 
         // Pages
         Route::get('/{bookSlug}/create-page', 'PageController@create');
@@ -57,9 +55,9 @@ Route::group(['middleware' => 'auth'], function () {
         Route::get('/{bookSlug}/draft/{pageId}', 'PageController@editDraft');
         Route::post('/{bookSlug}/draft/{pageId}', 'PageController@store');
         Route::get('/{bookSlug}/page/{pageSlug}', 'PageController@show');
-        Route::get('/{bookSlug}/page/{pageSlug}/export/pdf', 'PageController@exportPdf');
-        Route::get('/{bookSlug}/page/{pageSlug}/export/html', 'PageController@exportHtml');
-        Route::get('/{bookSlug}/page/{pageSlug}/export/plaintext', 'PageController@exportPlainText');
+        Route::get('/{bookSlug}/page/{pageSlug}/export/pdf', 'PageExportController@pdf');
+        Route::get('/{bookSlug}/page/{pageSlug}/export/html', 'PageExportController@html');
+        Route::get('/{bookSlug}/page/{pageSlug}/export/plaintext', 'PageExportController@plainText');
         Route::get('/{bookSlug}/page/{pageSlug}/edit', 'PageController@edit');
         Route::get('/{bookSlug}/page/{pageSlug}/move', 'PageController@showMove');
         Route::put('/{bookSlug}/page/{pageSlug}/move', 'PageController@move');
@@ -74,11 +72,11 @@ Route::group(['middleware' => 'auth'], function () {
         Route::delete('/{bookSlug}/draft/{pageId}', 'PageController@destroyDraft');
 
         // Revisions
-        Route::get('/{bookSlug}/page/{pageSlug}/revisions', 'PageController@showRevisions');
-        Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}', 'PageController@showRevision');
-        Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}/changes', 'PageController@showRevisionChanges');
-        Route::put('/{bookSlug}/page/{pageSlug}/revisions/{revId}/restore', 'PageController@restoreRevision');
-        Route::delete('/{bookSlug}/page/{pageSlug}/revisions/{revId}/delete', 'PageController@destroyRevision');
+        Route::get('/{bookSlug}/page/{pageSlug}/revisions', 'PageRevisionController@index');
+        Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}', 'PageRevisionController@show');
+        Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}/changes', 'PageRevisionController@changes');
+        Route::put('/{bookSlug}/page/{pageSlug}/revisions/{revId}/restore', 'PageRevisionController@restore');
+        Route::delete('/{bookSlug}/page/{pageSlug}/revisions/{revId}/delete', 'PageRevisionController@destroy');
 
         // Chapters
         Route::get('/{bookSlug}/chapter/{chapterSlug}/create-page', 'PageController@create');
@@ -91,9 +89,9 @@ Route::group(['middleware' => 'auth'], function () {
         Route::put('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@move');
         Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit');
         Route::get('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@showPermissions');
-        Route::get('/{bookSlug}/chapter/{chapterSlug}/export/pdf', 'ChapterController@exportPdf');
-        Route::get('/{bookSlug}/chapter/{chapterSlug}/export/html', 'ChapterController@exportHtml');
-        Route::get('/{bookSlug}/chapter/{chapterSlug}/export/plaintext', 'ChapterController@exportPlainText');
+        Route::get('/{bookSlug}/chapter/{chapterSlug}/export/pdf', 'ChapterExportController@pdf');
+        Route::get('/{bookSlug}/chapter/{chapterSlug}/export/html', 'ChapterExportController@html');
+        Route::get('/{bookSlug}/chapter/{chapterSlug}/export/plaintext', 'ChapterExportController@plainText');
         Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@permissions');
         Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete');
         Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy');
@@ -158,6 +156,9 @@ Route::group(['middleware' => 'auth'], function () {
     Route::get('/search/chapter/{bookId}', 'SearchController@searchChapter');
     Route::get('/search/entity/siblings', 'SearchController@searchSiblings');
 
+    Route::get('/templates', 'PageTemplateController@list');
+    Route::get('/templates/{templateId}', 'PageTemplateController@get');
+
     // Other Pages
     Route::get('/', 'HomeController@index');
     Route::get('/home', 'HomeController@index');
@@ -171,6 +172,7 @@ Route::group(['middleware' => 'auth'], function () {
         // Maintenance
         Route::get('/maintenance', 'SettingController@showMaintenance');
         Route::delete('/maintenance/cleanup-images', 'SettingController@cleanupImages');
+        Route::post('/maintenance/send-test-email', 'SettingController@sendTestEmail');
 
         // Users
         Route::get('/users', 'UserController@index');
@@ -208,12 +210,16 @@ Route::get('/login', 'Auth\LoginController@getLogin');
 Route::post('/login', 'Auth\LoginController@login');
 Route::get('/logout', 'Auth\LoginController@logout');
 Route::get('/register', 'Auth\RegisterController@getRegister');
-Route::get('/register/confirm', 'Auth\RegisterController@getRegisterConfirmation');
-Route::get('/register/confirm/awaiting', 'Auth\RegisterController@showAwaitingConfirmation');
-Route::post('/register/confirm/resend', 'Auth\RegisterController@resendConfirmation');
-Route::get('/register/confirm/{token}', 'Auth\RegisterController@confirmEmail');
+Route::get('/register/confirm', 'Auth\ConfirmEmailController@show');
+Route::get('/register/confirm/awaiting', 'Auth\ConfirmEmailController@showAwaiting');
+Route::post('/register/confirm/resend', 'Auth\ConfirmEmailController@resend');
+Route::get('/register/confirm/{token}', 'Auth\ConfirmEmailController@confirm');
 Route::post('/register', 'Auth\RegisterController@postRegister');
 
+// User invitation routes
+Route::get('/register/invite/{token}', 'Auth\UserInviteController@showSetPassword');
+Route::post('/register/invite/{token}', 'Auth\UserInviteController@setPassword');
+
 // Password reset link request routes...
 Route::get('/password/email', 'Auth\ForgotPasswordController@showLinkRequestForm');
 Route::post('/password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
index c96a04f008ee21e260b28f7701595ed59e2839e3..869804c2ad0b113b8edfc31984e4e94c5dedcf0d 100755 (executable)
@@ -1,2 +1,3 @@
 *
+!data/
 !.gitignore
\ No newline at end of file
index 3d36d85b2e65d926de8c85b9781579eab2cc4f2c..eb83faded44edc67d721e595d1a11d3861e12255 100644 (file)
@@ -1,8 +1,8 @@
 <?php namespace Tests;
 
+use BookStack\Auth\User;
 use BookStack\Entities\Page;
 use BookStack\Notifications\ConfirmEmail;
-use BookStack\Auth\User;
 use BookStack\Settings\SettingService;
 use Illuminate\Support\Facades\Notification;
 
@@ -81,7 +81,7 @@ class AuthTest extends BrowserKitTest
             ->press('Create Account')
             ->see('The name must be at least 2 characters.')
             ->see('The email must be a valid email address.')
-            ->see('The password must be at least 6 characters.')
+            ->see('The password must be at least 8 characters.')
             ->seePageIs('/register');
     }
 
@@ -276,6 +276,15 @@ class AuthTest extends BrowserKitTest
     public function test_user_cannot_be_deleted_if_last_admin()
     {
         $adminRole = \BookStack\Auth\Role::getRole('admin');
+
+        // Delete all but one admin user if there are more than one
+        $adminUsers = $adminRole->users;
+        if (count($adminUsers) > 1) {
+            foreach ($adminUsers->splice(1) as $user) {
+                $user->delete();
+            }
+        }
+
         // Ensure we currently only have 1 admin user
         $this->assertEquals(1, $adminRole->users()->count());
         $user = $adminRole->users->first();
index 5923ef377700734f931b6a56098bfe14a18e255d..fe28698dfac6b79013020c43fb82a93f1bdcb8fe 100644 (file)
@@ -15,7 +15,7 @@ class LdapTest extends BrowserKitTest
     protected $mockUser;
     protected $resourceId = 'resource-test';
 
-    public function setUp()
+    public function setUp(): void
     {
         parent::setUp();
         if (!defined('LDAP_OPT_REFERRALS')) define('LDAP_OPT_REFERRALS', 1);
index b8ca81174c3911663c08e703266640ebd1abb212..526c0e199a184cf7ee0a7f74c49fcea0bf42cbba 100644 (file)
@@ -153,7 +153,7 @@ class SocialAuthTest extends TestCase
         config()->set('services.google.select_account', 'true');
 
         $resp = $this->get('/login/service/google');
-        $this->assertContains('prompt=select_account', $resp->headers->get('Location'));
+        $this->assertStringContainsString('prompt=select_account', $resp->headers->get('Location'));
     }
 
 }
diff --git a/tests/Auth/UserInviteTest.php b/tests/Auth/UserInviteTest.php
new file mode 100644 (file)
index 0000000..d200134
--- /dev/null
@@ -0,0 +1,114 @@
+<?php namespace Tests;
+
+
+use BookStack\Auth\Access\UserInviteService;
+use BookStack\Auth\User;
+use BookStack\Notifications\UserInvite;
+use Carbon\Carbon;
+use DB;
+use Illuminate\Support\Str;
+use Notification;
+
+class UserInviteTest extends TestCase
+{
+
+    public function test_user_creation_creates_invite()
+    {
+        Notification::fake();
+        $admin = $this->getAdmin();
+
+        $this->actingAs($admin)->post('/settings/users/create', [
+            'name' => 'Barry',
+            'email' => '[email protected]',
+            'send_invite' => 'true',
+        ]);
+
+        $newUser = User::query()->where('email', '=', '[email protected]')->orderBy('id', 'desc')->first();
+
+        Notification::assertSentTo($newUser, UserInvite::class);
+        $this->assertDatabaseHas('user_invites', [
+            'user_id' => $newUser->id
+        ]);
+    }
+
+    public function test_invite_set_password()
+    {
+        Notification::fake();
+        $user = $this->getViewer();
+        $inviteService = app(UserInviteService::class);
+
+        $inviteService->sendInvitation($user);
+        $token = DB::table('user_invites')->where('user_id', '=', $user->id)->first()->token;
+
+        $setPasswordPageResp = $this->get('/register/invite/' . $token);
+        $setPasswordPageResp->assertSuccessful();
+        $setPasswordPageResp->assertSee('Welcome to BookStack!');
+        $setPasswordPageResp->assertSee('Password');
+        $setPasswordPageResp->assertSee('Confirm Password');
+
+        $setPasswordResp = $this->followingRedirects()->post('/register/invite/' . $token, [
+            'password' => 'my test password',
+        ]);
+        $setPasswordResp->assertSee('Password set, you now have access to BookStack!');
+        $newPasswordValid = auth()->validate([
+            'email' => $user->email,
+            'password' => 'my test password'
+        ]);
+        $this->assertTrue($newPasswordValid);
+        $this->assertDatabaseMissing('user_invites', [
+            'user_id' => $user->id
+        ]);
+    }
+
+    public function test_invite_set_has_password_validation()
+    {
+        Notification::fake();
+        $user = $this->getViewer();
+        $inviteService = app(UserInviteService::class);
+
+        $inviteService->sendInvitation($user);
+        $token = DB::table('user_invites')->where('user_id', '=', $user->id)->first()->token;
+
+        $this->get('/register/invite/' . $token);
+        $shortPassword = $this->followingRedirects()->post('/register/invite/' . $token, [
+            'password' => 'mypassw',
+        ]);
+        $shortPassword->assertSee('The password must be at least 8 characters.');
+
+        $this->get('/register/invite/' . $token);
+        $noPassword = $this->followingRedirects()->post('/register/invite/' . $token, [
+            'password' => '',
+        ]);
+        $noPassword->assertSee('The password field is required.');
+
+        $this->assertDatabaseHas('user_invites', [
+            'user_id' => $user->id
+        ]);
+    }
+
+    public function test_non_existent_invite_token_redirects_to_home()
+    {
+        $setPasswordPageResp = $this->get('/register/invite/' . Str::random(12));
+        $setPasswordPageResp->assertRedirect('/');
+
+        $setPasswordResp = $this->post('/register/invite/' . Str::random(12), ['password' => 'Password Test']);
+        $setPasswordResp->assertRedirect('/');
+    }
+
+    public function test_token_expires_after_two_weeks()
+    {
+        Notification::fake();
+        $user = $this->getViewer();
+        $inviteService = app(UserInviteService::class);
+
+        $inviteService->sendInvitation($user);
+        $tokenEntry = DB::table('user_invites')->where('user_id', '=', $user->id)->first();
+        DB::table('user_invites')->update(['created_at' => Carbon::now()->subDays(14)->subHour(1)]);
+
+        $setPasswordPageResp = $this->get('/register/invite/' . $tokenEntry->token);
+        $setPasswordPageResp->assertRedirect('/password/email');
+        $setPasswordPageResp->assertSessionHas('error', 'This invitation link has expired. You can instead try to reset your account password.');
+    }
+
+
+}
\ No newline at end of file
index ab0d9d898b31db9a9f167fc7a4b9131ffe722a1f..b81afe31106025352a3d5c50a23b2162b1ae6d5f 100644 (file)
@@ -21,7 +21,7 @@ abstract class BrowserKitTest extends TestCase
      */
     protected $baseUrl = 'http://localhost';
 
-    public function tearDown()
+    public function tearDown() : void
     {
         \DB::disconnect();
         parent::tearDown();
index a884809696d32cd4e4126f7c7c68121738ee905f..4aef0ed2658861b2fe8a6f326c12ea131e3888ee 100644 (file)
@@ -2,7 +2,6 @@
 
 use BookStack\Auth\Permissions\JointPermission;
 use BookStack\Entities\Page;
-use BookStack\Entities\Repos\EntityRepo;
 use BookStack\Auth\User;
 use BookStack\Entities\Repos\PageRepo;
 
@@ -56,7 +55,7 @@ class CommandsTest extends TestCase
         $this->asEditor();
         $pageRepo = app(PageRepo::class);
         $page = Page::first();
-        $pageRepo->updatePage($page, $page->book_id, ['name' => 'updated page', 'html' => '<p>new content</p>', 'summary' => 'page revision testing']);
+        $pageRepo->update($page, ['name' => 'updated page', 'html' => '<p>new content</p>', 'summary' => 'page revision testing']);
         $pageRepo->updatePageDraft($page, ['name' => 'updated page', 'html' => '<p>new content in draft</p>', 'summary' => 'page revision testing']);
 
         $this->assertDatabaseHas('page_revisions', [
index 158fb5ca184e2075e0cef15339bf71075576dc12..5c7673847140eb71d284e14d94d92921f6b706c6 100644 (file)
@@ -4,6 +4,7 @@ use BookStack\Auth\Role;
 use BookStack\Auth\User;
 use BookStack\Entities\Book;
 use BookStack\Entities\Bookshelf;
+use Illuminate\Support\Str;
 
 class BookShelfTest extends TestCase
 {
@@ -55,8 +56,8 @@ class BookShelfTest extends TestCase
     {
         $booksToInclude = Book::take(2)->get();
         $shelfInfo = [
-            'name' => 'My test book' . str_random(4),
-            'description' => 'Test book description ' . str_random(10)
+            'name' => 'My test book' . Str::random(4),
+            'description' => 'Test book description ' . Str::random(10)
         ];
         $resp = $this->asEditor()->post('/shelves', array_merge($shelfInfo, [
             'books' => $booksToInclude->implode('id', ','),
@@ -120,8 +121,8 @@ class BookShelfTest extends TestCase
 
         $booksToInclude = Book::take(2)->get();
         $shelfInfo = [
-            'name' => 'My test book' . str_random(4),
-            'description' => 'Test book description ' . str_random(10)
+            'name' => 'My test book' . Str::random(4),
+            'description' => 'Test book description ' . Str::random(10)
         ];
 
         $resp = $this->asEditor()->put($shelf->getUrl(), array_merge($shelfInfo, [
index 2683f57cb601767243b745edd31b2caf1ea4e457..967e550a735e6294faf970bcfc1a605b8a57c2f7 100644 (file)
@@ -3,7 +3,7 @@
 class CommentSettingTest extends BrowserKitTest {
   protected $page;
 
-  public function setUp() {
+  public function setUp(): void {
       parent::setUp();
       $this->page = \BookStack\Entities\Page::first();
   }
index a3fb1cfe11833591a5d1282aeda43fdce23f9eb9..3d12ed7495943dcc75ef979d87d845a57c41ee47 100644 (file)
@@ -1,9 +1,9 @@
 <?php namespace Tests;
 
+use BookStack\Entities\Bookshelf;
 use BookStack\Entities\Book;
 use BookStack\Entities\Chapter;
 use BookStack\Entities\Page;
-use BookStack\Entities\Repos\EntityRepo;
 use BookStack\Auth\UserRepo;
 use BookStack\Entities\Repos\PageRepo;
 use Carbon\Carbon;
@@ -192,7 +192,7 @@ class EntityTest extends BrowserKitTest
         $entities = $this->createEntityChainBelongingToUser($creator, $updater);
         $this->actingAs($creator);
         app(UserRepo::class)->destroy($creator);
-        app(PageRepo::class)->savePageRevision($entities['page']);
+        app(PageRepo::class)->update($entities['page'], ['html' => '<p>hello!</p>>']);
 
         $this->checkEntitiesViewable($entities);
     }
@@ -205,7 +205,7 @@ class EntityTest extends BrowserKitTest
         $entities = $this->createEntityChainBelongingToUser($creator, $updater);
         $this->actingAs($updater);
         app(UserRepo::class)->destroy($updater);
-        app(PageRepo::class)->savePageRevision($entities['page']);
+        app(PageRepo::class)->update($entities['page'], ['html' => '<p>Hello there!</p>']);
 
         $this->checkEntitiesViewable($entities);
     }
@@ -273,8 +273,7 @@ class EntityTest extends BrowserKitTest
 
     public function test_slug_multi_byte_lower_casing()
     {
-        $entityRepo = app(EntityRepo::class);
-        $book = $entityRepo->createFromInput('book', [
+        $book = $this->newBook([
             'name' => 'КНИГА'
         ]);
 
@@ -284,12 +283,36 @@ class EntityTest extends BrowserKitTest
 
     public function test_slug_format()
     {
-        $entityRepo = app(EntityRepo::class);
-        $book = $entityRepo->createFromInput('book', [
+        $book = $this->newBook([
             'name' => 'PartA / PartB / PartC'
         ]);
 
         $this->assertEquals('parta-partb-partc', $book->slug);
     }
 
+    public function test_shelf_cancel_creation_returns_to_correct_place()
+    {
+        $shelf = Bookshelf::first();
+
+        // Cancel button from shelf goes back to shelf
+        $this->asEditor()->visit($shelf->getUrl('/create-book'))
+            ->see('Cancel')
+            ->click('Cancel')
+            ->seePageIs($shelf->getUrl());
+
+        // Cancel button from books goes back to books
+        $this->asEditor()->visit('/create-book')
+            ->see('Cancel')
+            ->click('Cancel')
+            ->seePageIs('/books');
+
+        // Cancel button from book edit goes back to book
+        $book = Book::first();
+
+        $this->asEditor()->visit($book->getUrl('/edit'))
+            ->see('Cancel')
+            ->click('Cancel')
+            ->seePageIs($book->getUrl());
+    }
+
 }
index e3a74f64d1d2c306df662071b369beadfd9bfbac..9a2d32028e4b26887c7d231b68df2b1887668e3a 100644 (file)
@@ -4,6 +4,7 @@
 use BookStack\Entities\Chapter;
 use BookStack\Entities\Page;
 use BookStack\Uploads\HttpFetcher;
+use Illuminate\Support\Str;
 
 class ExportTest extends TestCase
 {
@@ -79,7 +80,7 @@ class ExportTest extends TestCase
 
     public function test_book_html_export_shows_chapter_descriptions()
     {
-        $chapterDesc = 'My custom test chapter description ' . str_random(12);
+        $chapterDesc = 'My custom test chapter description ' . Str::random(12);
         $chapter = Chapter::query()->first();
         $chapter->description = $chapterDesc;
         $chapter->save();
index c481e444f59cf563f25791bd3725bac74ee6df1c..5d3af4f6e26705b8fed3e617a78d9104fe3fb99d 100644 (file)
@@ -4,7 +4,7 @@ class MarkdownTest extends BrowserKitTest
 {
     protected $page;
 
-    public function setUp()
+    public function setUp(): void
     {
         parent::setUp();
         $this->page = \BookStack\Entities\Page::first();
index c80b5f1d96f1b9c85ec5bf281c42e7f0c3f17895..8a78c8ac019fd08de3cc37d50e7df50954c83f6e 100644 (file)
@@ -1,8 +1,7 @@
 <?php namespace Tests;
 
+use BookStack\Entities\Managers\PageContent;
 use BookStack\Entities\Page;
-use BookStack\Entities\Repos\EntityRepo;
-use BookStack\Entities\Repos\PageRepo;
 
 class PageContentTest extends TestCase
 {
@@ -50,7 +49,7 @@ class PageContentTest extends TestCase
         $resp->assertStatus(302);
 
         $page = Page::find($page->id);
-        $this->assertContains($includeTag, $page->html);
+        $this->assertStringContainsString($includeTag, $page->html);
         $this->assertEquals('', $page->text);
     }
 
@@ -80,6 +79,7 @@ class PageContentTest extends TestCase
         $page->save();
 
         $pageView = $this->get($page->getUrl());
+        $pageView->assertStatus(200);
         $pageView->assertDontSee($script);
         $pageView->assertSee('abc123abc123');
     }
@@ -103,12 +103,42 @@ class PageContentTest extends TestCase
             $page->save();
 
             $pageView = $this->get($page->getUrl());
+            $pageView->assertStatus(200);
             $pageView->assertElementNotContains('.page-content', '<script>');
             $pageView->assertElementNotContains('.page-content', '</script>');
         }
 
     }
 
+    public function test_iframe_js_and_base64_urls_are_removed()
+    {
+        $checks = [
+            '<iframe src="javascript:alert(document.cookie)"></iframe>',
+            '<iframe SRC=" javascript: alert(document.cookie)"></iframe>',
+            '<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
+            '<iframe src=" data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
+            '<iframe srcdoc="<script>window.alert(document.cookie)</script>"></iframe>'
+        ];
+
+        $this->asEditor();
+        $page = Page::first();
+
+        foreach ($checks as $check) {
+            $page->html = $check;
+            $page->save();
+
+            $pageView = $this->get($page->getUrl());
+            $pageView->assertStatus(200);
+            $pageView->assertElementNotContains('.page-content', '<iframe>');
+            $pageView->assertElementNotContains('.page-content', '</iframe>');
+            $pageView->assertElementNotContains('.page-content', 'src=');
+            $pageView->assertElementNotContains('.page-content', 'javascript:');
+            $pageView->assertElementNotContains('.page-content', 'data:');
+            $pageView->assertElementNotContains('.page-content', 'base64');
+        }
+
+    }
+
     public function test_page_inline_on_attributes_removed_by_default()
     {
         $this->asEditor();
@@ -118,6 +148,7 @@ class PageContentTest extends TestCase
         $page->save();
 
         $pageView = $this->get($page->getUrl());
+        $pageView->assertStatus(200);
         $pageView->assertDontSee($script);
         $pageView->assertSee('<p>Hello</p>');
     }
@@ -130,6 +161,7 @@ class PageContentTest extends TestCase
             '<div>Lorem ipsum dolor sit amet.<p onclick="console.log(\'test\')">Hello</p></div>',
             '<div><div><div><div>Lorem ipsum dolor sit amet.<p onclick="console.log(\'test\')">Hello</p></div></div></div></div>',
             '<div onclick="console.log(\'test\')">Lorem ipsum dolor sit amet.</div><p onclick="console.log(\'test\')">Hello</p><div></div>',
+            '<a a="<img src=1 onerror=\'alert(1)\'> ',
         ];
 
         $this->asEditor();
@@ -140,6 +172,7 @@ class PageContentTest extends TestCase
             $page->save();
 
             $pageView = $this->get($page->getUrl());
+            $pageView->assertStatus(200);
             $pageView->assertElementNotContains('.page-content', 'onclick');
         }
 
@@ -208,4 +241,66 @@ class PageContentTest extends TestCase
         $updatedPage = Page::where('id', '=', $page->id)->first();
         $this->assertEquals(substr_count($updatedPage->html, "bkmrk-test\""), 1);
     }
+
+    public function test_get_page_nav_sets_correct_properties()
+    {
+        $content = '<h1 id="testa">Hello</h1><h2 id="testb">There</h2><h3 id="testc">Donkey</h3>';
+        $pageContent = new PageContent(new Page(['html' => $content]));
+        $navMap = $pageContent->getNavigation($content);
+
+        $this->assertCount(3, $navMap);
+        $this->assertArrayMapIncludes([
+            'nodeName' => 'h1',
+            'link' => '#testa',
+            'text' => 'Hello',
+            'level' => 1,
+        ], $navMap[0]);
+        $this->assertArrayMapIncludes([
+            'nodeName' => 'h2',
+            'link' => '#testb',
+            'text' => 'There',
+            'level' => 2,
+        ], $navMap[1]);
+        $this->assertArrayMapIncludes([
+            'nodeName' => 'h3',
+            'link' => '#testc',
+            'text' => 'Donkey',
+            'level' => 3,
+        ], $navMap[2]);
+    }
+
+    public function test_get_page_nav_does_not_show_empty_titles()
+    {
+        $content = '<h1 id="testa">Hello</h1><h2 id="testb">&nbsp;</h2><h3 id="testc"></h3>';
+        $pageContent = new PageContent(new Page(['html' => $content]));
+        $navMap = $pageContent->getNavigation($content);
+
+        $this->assertCount(1, $navMap);
+        $this->assertArrayMapIncludes([
+            'nodeName' => 'h1',
+            'link' => '#testa',
+            'text' => 'Hello'
+        ], $navMap[0]);
+    }
+
+    public function test_get_page_nav_shifts_headers_if_only_smaller_ones_are_used()
+    {
+        $content = '<h4 id="testa">Hello</h4><h5 id="testb">There</h5><h6 id="testc">Donkey</h6>';
+        $pageContent = new PageContent(new Page(['html' => $content]));
+        $navMap = $pageContent->getNavigation($content);
+
+        $this->assertCount(3, $navMap);
+        $this->assertArrayMapIncludes([
+            'nodeName' => 'h4',
+            'level' => 1,
+        ], $navMap[0]);
+        $this->assertArrayMapIncludes([
+            'nodeName' => 'h5',
+            'level' => 2,
+        ], $navMap[1]);
+        $this->assertArrayMapIncludes([
+            'nodeName' => 'h6',
+            'level' => 3,
+        ], $navMap[2]);
+    }
 }
index f15651f39b672824520bbdb096962cc2fe81b256..e83f78a10a8a8f2d566ece9586a115dd24d4c323 100644 (file)
@@ -1,14 +1,17 @@
 <?php namespace Tests;
 
-
 use BookStack\Entities\Repos\PageRepo;
 
 class PageDraftTest extends BrowserKitTest
 {
     protected $page;
+
+    /**
+     * @var PageRepo
+     */
     protected $pageRepo;
 
-    public function setUp()
+    public function setUp(): void
     {
         parent::setUp();
         $this->page = \BookStack\Entities\Page::first();
@@ -18,25 +21,26 @@ class PageDraftTest extends BrowserKitTest
     public function test_draft_content_shows_if_available()
     {
         $addedContent = '<p>test message content</p>';
-        $this->asAdmin()->visit($this->page->getUrl() . '/edit')
+        $this->asAdmin()->visit($this->page->getUrl('/edit'))
             ->dontSeeInField('html', $addedContent);
 
         $newContent = $this->page->html . $addedContent;
         $this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]);
-        $this->asAdmin()->visit($this->page->getUrl() . '/edit')
+        $this->asAdmin()->visit($this->page->getUrl('/edit'))
             ->seeInField('html', $newContent);
     }
 
     public function test_draft_not_visible_by_others()
     {
         $addedContent = '<p>test message content</p>';
-        $this->asAdmin()->visit($this->page->getUrl() . '/edit')
+        $this->asAdmin()->visit($this->page->getUrl('/edit'))
             ->dontSeeInField('html', $addedContent);
 
         $newContent = $this->page->html . $addedContent;
         $newUser = $this->getEditor();
         $this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]);
-        $this->actingAs($newUser)->visit($this->page->getUrl() . '/edit')
+
+        $this->actingAs($newUser)->visit($this->page->getUrl('/edit'))
             ->dontSeeInField('html', $newContent);
     }
 
@@ -44,7 +48,7 @@ class PageDraftTest extends BrowserKitTest
     {
         $this->asAdmin();
         $this->pageRepo->updatePageDraft($this->page, ['html' => 'test content']);
-        $this->asAdmin()->visit($this->page->getUrl() . '/edit')
+        $this->asAdmin()->visit($this->page->getUrl('/edit'))
             ->see('You are currently editing a draft');
     }
 
@@ -52,7 +56,7 @@ class PageDraftTest extends BrowserKitTest
     {
         $nonEditedPage = \BookStack\Entities\Page::take(10)->get()->last();
         $addedContent = '<p>test message content</p>';
-        $this->asAdmin()->visit($this->page->getUrl() . '/edit')
+        $this->asAdmin()->visit($this->page->getUrl('/edit'))
             ->dontSeeInField('html', $addedContent);
 
         $newContent = $this->page->html . $addedContent;
@@ -60,7 +64,7 @@ class PageDraftTest extends BrowserKitTest
         $this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]);
 
         $this->actingAs($newUser)
-            ->visit($this->page->getUrl() . '/edit')
+            ->visit($this->page->getUrl('/edit'))
             ->see('Admin has started editing this page');
             $this->flushSession();
         $this->visit($nonEditedPage->getUrl() . '/edit')
@@ -84,11 +88,11 @@ class PageDraftTest extends BrowserKitTest
         $newUser = $this->getEditor();
 
         $this->actingAs($newUser)->visit('/')
-            ->visit($book->getUrl() . '/create-page')
-            ->visit($chapter->getUrl() . '/create-page')
+            ->visit($book->getUrl('/create-page'))
+            ->visit($chapter->getUrl('/create-page'))
             ->visit($book->getUrl())
             ->seeInElement('.book-contents', 'New Page');
-        
+
         $this->asAdmin()
             ->visit($book->getUrl())
             ->dontSeeInElement('.book-contents', 'New Page')
index 521ea79a4760ee6bcfa30c3497b8882f1a267ddb..38193ec1a6b7bd09cadbe04e6a8bcb3ea8765f98 100644 (file)
@@ -12,7 +12,7 @@ class PageRevisionTest extends TestCase
 
         $pageRepo = app(PageRepo::class);
         $page = Page::first();
-        $pageRepo->updatePage($page, $page->book_id, ['name' => 'updated page', 'html' => '<p>new content</p>', 'summary' => 'page revision testing']);
+        $pageRepo->update($page, ['name' => 'updated page', 'html' => '<p>new content</p>', 'summary' => 'page revision testing']);
         $pageRevision = $page->revisions->last();
 
         $revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id);
@@ -30,8 +30,8 @@ class PageRevisionTest extends TestCase
 
         $pageRepo = app(PageRepo::class);
         $page = Page::first();
-        $pageRepo->updatePage($page, $page->book_id, ['name' => 'updated page abc123', 'html' => '<p>new contente def456</p>', 'summary' => 'initial page revision testing']);
-        $pageRepo->updatePage($page, $page->book_id, ['name' => 'updated page again', 'html' => '<p>new content</p>', 'summary' => 'page revision testing']);
+        $pageRepo->update($page, ['name' => 'updated page abc123', 'html' => '<p>new contente def456</p>', 'summary' => 'initial page revision testing']);
+        $pageRepo->update($page, ['name' => 'updated page again', 'html' => '<p>new content</p>', 'summary' => 'page revision testing']);
         $page =  Page::find($page->id);
 
 
@@ -87,7 +87,7 @@ class PageRevisionTest extends TestCase
         // Delete the first revision
         $revision = $page->revisions->get(1);
         $resp = $this->asEditor()->delete($revision->getUrl('/delete/'));
-        $resp->assertStatus(200);
+        $resp->assertRedirect($page->getUrl('/revisions'));
 
         $page = Page::find($page->id);
         $afterRevisionCount = $page->revisions->count();
@@ -98,7 +98,7 @@ class PageRevisionTest extends TestCase
         $beforeRevisionCount = $page->revisions->count();
         $currentRevision = $page->getCurrentRevision();
         $resp = $this->asEditor()->delete($currentRevision->getUrl('/delete/'));
-        $resp->assertStatus(400);
+        $resp->assertRedirect($page->getUrl('/revisions'));
 
         $page = Page::find($page->id);
         $afterRevisionCount = $page->revisions->count();
diff --git a/tests/Entity/PageTemplateTest.php b/tests/Entity/PageTemplateTest.php
new file mode 100644 (file)
index 0000000..883de4a
--- /dev/null
@@ -0,0 +1,90 @@
+<?php namespace Entity;
+
+use BookStack\Entities\Page;
+use Tests\TestCase;
+
+class PageTemplateTest extends TestCase
+{
+    public function test_active_templates_visible_on_page_view()
+    {
+        $page = Page::first();
+
+        $this->asEditor();
+        $templateView = $this->get($page->getUrl());
+        $templateView->assertDontSee('Page Template');
+
+        $page->template = true;
+        $page->save();
+
+        $templateView = $this->get($page->getUrl());
+        $templateView->assertSee('Page Template');
+    }
+
+    public function test_manage_templates_permission_required_to_change_page_template_status()
+    {
+        $page = Page::first();
+        $editor = $this->getEditor();
+        $this->actingAs($editor);
+
+        $pageUpdateData = [
+            'name' => $page->name,
+            'html' => $page->html,
+            'template' => 'true',
+        ];
+
+        $this->put($page->getUrl(), $pageUpdateData);
+        $this->assertDatabaseHas('pages', [
+            'id' => $page->id,
+            'template' => false,
+        ]);
+
+        $this->giveUserPermissions($editor, ['templates-manage']);
+
+        $this->put($page->getUrl(), $pageUpdateData);
+        $this->assertDatabaseHas('pages', [
+            'id' => $page->id,
+            'template' => true,
+        ]);
+    }
+
+    public function test_templates_content_should_be_fetchable_only_if_page_marked_as_template()
+    {
+        $content = '<div>my_custom_template_content</div>';
+        $page = Page::first();
+        $editor = $this->getEditor();
+        $this->actingAs($editor);
+
+        $templateFetch = $this->get('/templates/' . $page->id);
+        $templateFetch->assertStatus(404);
+
+        $page->html = $content;
+        $page->template = true;
+        $page->save();
+
+        $templateFetch = $this->get('/templates/' . $page->id);
+        $templateFetch->assertStatus(200);
+        $templateFetch->assertJson([
+            'html' => $content,
+            'markdown' => '',
+        ]);
+    }
+
+    public function test_template_endpoint_returns_paginated_list_of_templates()
+    {
+        $editor = $this->getEditor();
+        $this->actingAs($editor);
+
+        $toBeTemplates = Page::query()->orderBy('name', 'asc')->take(12)->get();
+        $page = $toBeTemplates->first();
+
+        $emptyTemplatesFetch = $this->get('/templates');
+        $emptyTemplatesFetch->assertDontSee($page->name);
+
+        Page::query()->whereIn('id', $toBeTemplates->pluck('id')->toArray())->update(['template' => true]);
+
+        $templatesFetch = $this->get('/templates');
+        $templatesFetch->assertSee($page->name);
+        $templatesFetch->assertSee('pagination');
+    }
+
+}
\ No newline at end of file
index a3c20e84c5b517bede1e3b3488516a299c1e853a..7d67f05c6d6b0b77be7dde916d3c497c512d0482 100644 (file)
@@ -1,6 +1,5 @@
 <?php namespace Tests;
 
-use BookStack\Auth\Role;
 use BookStack\Entities\Book;
 use BookStack\Entities\Chapter;
 use BookStack\Entities\Page;
@@ -10,7 +9,7 @@ class SortTest extends TestCase
 {
     protected $book;
 
-    public function setUp()
+    public function setUp(): void
     {
         parent::setUp();
         $this->book = Book::first();
@@ -20,7 +19,7 @@ class SortTest extends TestCase
     {
         $this->asAdmin();
         $pageRepo = app(PageRepo::class);
-        $draft = $pageRepo->getDraftPage($this->book);
+        $draft = $pageRepo->getNewDraftPage($this->book);
 
         $resp = $this->get($this->book->getUrl());
         $resp->assertSee($draft->name);
@@ -29,7 +28,7 @@ class SortTest extends TestCase
         $resp->assertDontSee($draft->name);
     }
 
-    public function test_page_move()
+    public function test_page_move_into_book()
     {
         $page = Page::first();
         $currentBook = $page->book;
@@ -51,6 +50,44 @@ class SortTest extends TestCase
         $newBookResp->assertSee($page->name);
     }
 
+    public function test_page_move_into_chapter()
+    {
+        $page = Page::first();
+        $currentBook = $page->book;
+        $newBook = Book::where('id', '!=', $currentBook->id)->first();
+        $newChapter = $newBook->chapters()->first();
+
+        $movePageResp = $this->actingAs($this->getEditor())->put($page->getUrl('/move'), [
+            'entity_selection' => 'chapter:' . $newChapter->id
+        ]);
+        $page = Page::find($page->id);
+
+        $movePageResp->assertRedirect($page->getUrl());
+        $this->assertTrue($page->book->id == $newBook->id, 'Page parent is now the new chapter');
+
+        $newChapterResp = $this->get($newChapter->getUrl());
+        $newChapterResp->assertSee($page->name);
+    }
+
+    public function test_page_move_from_chapter_to_book()
+    {
+        $oldChapter = Chapter::first();
+        $page = $oldChapter->pages()->first();
+        $newBook = Book::where('id', '!=', $oldChapter->book_id)->first();
+
+        $movePageResp = $this->actingAs($this->getEditor())->put($page->getUrl('/move'), [
+            'entity_selection' => 'book:' . $newBook->id
+        ]);
+        $page = Page::find($page->id);
+
+        $movePageResp->assertRedirect($page->getUrl());
+        $this->assertTrue($page->book->id == $newBook->id, 'Page parent is now the new book');
+        $this->assertTrue($page->chapter === null, 'Page has no parent chapter');
+
+        $newBookResp = $this->get($newBook->getUrl());
+        $newBookResp->assertSee($page->name);
+    }
+
     public function test_page_move_requires_create_permissions_on_parent()
     {
         $page = Page::first();
@@ -214,7 +251,6 @@ class SortTest extends TestCase
             'entity_selection' => 'book:' . $newBook->id,
             'name' => 'My copied test page'
         ]);
-
         $pageCopy = Page::where('name', '=', 'My copied test page')->first();
 
         $movePageResp->assertRedirect($pageCopy->getUrl());
index 70b8c960b73980544b74356d2666c0cd89fee067..13876410a0cddccaf4e3d04783e5735ccfd78697 100644 (file)
@@ -3,6 +3,7 @@
 use BookStack\Entities\Book;
 use BookStack\Entities\Chapter;
 use BookStack\Actions\Tag;
+use BookStack\Entities\Entity;
 use BookStack\Entities\Page;
 use BookStack\Auth\Permissions\PermissionService;
 
@@ -14,9 +15,9 @@ class TagTest extends BrowserKitTest
     /**
      * Get an instance of a page that has many tags.
      * @param \BookStack\Actions\Tag[]|bool $tags
-     * @return mixed
+     * @return Entity
      */
-    protected function getEntityWithTags($class, $tags = false)
+    protected function getEntityWithTags($class, $tags = false): Entity
     {
         $entity = $class::first();
 
@@ -122,7 +123,7 @@ class TagTest extends BrowserKitTest
         // Set restricted permission the page
         $page->restricted = true;
         $page->save();
-        $permissionService->buildJointPermissionsForEntity($page);
+        $page->rebuildPermissions();
 
         $this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']);
         $this->asEditor()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals([]);
index d9b8655ee1c67f83d827808fee236aacdebb40f4..cd68756ae4365ad27ef4499f6a363b96570b474a 100644 (file)
@@ -8,7 +8,7 @@ class LanguageTest extends TestCase
     /**
      * LanguageTest constructor.
      */
-    public function setUp()
+    public function setUp(): void
     {
         parent::setUp();
         $this->langs = array_diff(scandir(resource_path('lang')), ['..', '.', 'check.php', 'format.php']);
@@ -66,21 +66,4 @@ class LanguageTest extends TestCase
         $this->assertTrue(config('app.rtl'), "App RTL config should have been set to true by middleware");
     }
 
-    public function test_de_informal_falls_base_to_de()
-    {
-        // Base de back value
-        $deBack = trans()->get('common.cancel', [], 'de', false);
-        $this->assertEquals('Abbrechen', $deBack);
-        // Ensure de_informal has no value set
-        $this->assertEquals('common.cancel', trans()->get('common.cancel', [], 'de_informal', false));
-        // Ensure standard trans falls back to de
-        $this->assertEquals($deBack, trans('common.cancel', [], 'de_informal'));
-        // Ensure de_informal gets its own values where set
-        $deEmailActionHelp = trans()->get('common.email_action_help', [], 'de', false);
-        $enEmailActionHelp = trans()->get('common.email_action_help', [], 'en', false);
-        $deInformalEmailActionHelp = trans()->get('common.email_action_help', [], 'de_informal', false);
-        $this->assertNotEquals($deEmailActionHelp, $deInformalEmailActionHelp);
-        $this->assertNotEquals($enEmailActionHelp, $deInformalEmailActionHelp);
-    }
-
 }
\ No newline at end of file
index a7f681a37d1597d72a254d4e7b549e8c2e310202..d899c6396041a9d8a46d695efff0599ce0159a83 100644 (file)
@@ -5,14 +5,13 @@ use BookStack\Entities\Bookshelf;
 use BookStack\Entities\Chapter;
 use BookStack\Entities\Entity;
 use BookStack\Auth\User;
-use BookStack\Entities\Repos\EntityRepo;
 use BookStack\Entities\Page;
 
 class RestrictionsTest extends BrowserKitTest
 {
 
     /**
-     * @var \BookStack\Auth\User
+     * @var User
      */
     protected $user;
 
@@ -21,7 +20,7 @@ class RestrictionsTest extends BrowserKitTest
      */
     protected $viewer;
 
-    public function setUp()
+    public function setUp(): void
     {
         parent::setUp();
         $this->user = $this->getEditor();
@@ -327,7 +326,7 @@ class RestrictionsTest extends BrowserKitTest
 
     public function test_page_view_restriction()
     {
-        $page = \BookStack\Entities\Page::first();
+        $page = Page::first();
 
         $pageUrl = $page->getUrl();
         $this->actingAs($this->user)
@@ -367,7 +366,7 @@ class RestrictionsTest extends BrowserKitTest
 
     public function test_page_delete_restriction()
     {
-        $page = \BookStack\Entities\Page::first();
+        $page = Page::first();
 
         $pageUrl = $page->getUrl();
         $this->actingAs($this->user)
@@ -438,7 +437,7 @@ class RestrictionsTest extends BrowserKitTest
 
     public function test_page_restriction_form()
     {
-        $page = \BookStack\Entities\Page::first();
+        $page = Page::first();
         $this->asAdmin()->visit($page->getUrl() . '/permissions')
             ->see('Page Permissions')
             ->check('restricted')
@@ -665,10 +664,8 @@ class RestrictionsTest extends BrowserKitTest
         $this->setEntityRestrictions($firstBook, ['view', 'update']);
         $this->setEntityRestrictions($secondBook, ['view']);
 
-        $firstBookChapter = $this->app[EntityRepo::class]->createFromInput('chapter',
-                ['name' => 'first book chapter'], $firstBook);
-        $secondBookChapter = $this->app[EntityRepo::class]->createFromInput('chapter',
-                ['name' => 'second book chapter'], $secondBook);
+        $firstBookChapter = $this->newChapter(['name' => 'first book chapter'], $firstBook);
+        $secondBookChapter = $this->newChapter(['name' => 'second book chapter'], $secondBook);
 
         // Create request data
         $reqData = [
index 5bbdcf0bbb60c5f0c8ecf15d04f2285bbab5f7f5..371cffc0f3bd1be95a263c2bffd4c5f4277bb8f2 100644 (file)
@@ -11,7 +11,7 @@ class RolesTest extends BrowserKitTest
 {
     protected $user;
 
-    public function setUp()
+    public function setUp(): void
     {
         parent::setUp();
         $this->user = $this->getViewer();
@@ -119,6 +119,43 @@ class RolesTest extends BrowserKitTest
         $this->actingAs($this->user)->visit('/')->dontSee($usersLink);
     }
 
+    public function test_user_cannot_change_email_unless_they_have_manage_users_permission()
+    {
+        $userProfileUrl = '/settings/users/' . $this->user->id;
+        $originalEmail = $this->user->email;
+        $this->actingAs($this->user);
+
+        $this->visit($userProfileUrl)
+            ->assertResponseOk()
+            ->seeElement('input[name=email][disabled]');
+        $this->put($userProfileUrl, [
+            'name' => 'my_new_name',
+            'email' => '[email protected]',
+        ]);
+        $this->seeInDatabase('users', [
+            'id' => $this->user->id,
+            'email' => $originalEmail,
+            'name' => 'my_new_name',
+        ]);
+
+        $this->giveUserPermissions($this->user, ['users-manage']);
+
+        $this->visit($userProfileUrl)
+            ->assertResponseOk()
+            ->dontSeeElement('input[name=email][disabled]')
+            ->seeElement('input[name=email]');
+        $this->put($userProfileUrl, [
+            'name' => 'my_new_name_2',
+            'email' => '[email protected]',
+        ]);
+
+        $this->seeInDatabase('users', [
+            'id' => $this->user->id,
+            'email' => '[email protected]',
+            'name' => 'my_new_name_2',
+        ]);
+    }
+
     public function test_user_roles_manage_permission()
     {
         $this->actingAs($this->user)->visit('/settings/roles')
index 1d87e942aaf2167e10dbaed48f0a20f887511b94..3433f3b832f86d61411729ea23983905596d57d0 100644 (file)
@@ -1,17 +1,23 @@
 <?php namespace Tests;
 
+use BookStack\Auth\User;
 use BookStack\Entities\Book;
 use BookStack\Entities\Bookshelf;
 use BookStack\Entities\Chapter;
 use BookStack\Entities\Entity;
 use BookStack\Entities\Page;
-use BookStack\Entities\Repos\EntityRepo;
+use BookStack\Entities\Repos\BookRepo;
+use BookStack\Entities\Repos\BookshelfRepo;
+use BookStack\Entities\Repos\ChapterRepo;
 use BookStack\Auth\Permissions\PermissionsRepo;
 use BookStack\Auth\Role;
 use BookStack\Auth\Permissions\PermissionService;
 use BookStack\Entities\Repos\PageRepo;
 use BookStack\Settings\SettingService;
 use BookStack\Uploads\HttpFetcher;
+use Illuminate\Support\Env;
+use Mockery;
+use Throwable;
 
 trait SharedTestHelpers
 {
@@ -69,7 +75,7 @@ trait SharedTestHelpers
      */
     protected function getViewer($attributes = [])
     {
-        $user = \BookStack\Auth\Role::getRole('viewer')->users()->first();
+        $user = Role::getRole('viewer')->users()->first();
         if (!empty($attributes)) $user->forceFill($attributes)->save();
         return $user;
     }
@@ -77,20 +83,21 @@ trait SharedTestHelpers
     /**
      * Regenerate the permission for an entity.
      * @param Entity $entity
+     * @throws Throwable
      */
     protected function regenEntityPermissions(Entity $entity)
     {
-        app(PermissionService::class)->buildJointPermissionsForEntity($entity);
+        $entity->rebuildPermissions();
         $entity->load('jointPermissions');
     }
 
     /**
      * Create and return a new bookshelf.
      * @param array $input
-     * @return \BookStack\Entities\Bookshelf
+     * @return Bookshelf
      */
     public function newShelf($input = ['name' => 'test shelf', 'description' => 'My new test shelf']) {
-        return app(EntityRepo::class)->createFromInput('bookshelf', $input, false);
+        return app(BookshelfRepo::class)->create($input, []);
     }
 
     /**
@@ -99,29 +106,30 @@ trait SharedTestHelpers
      * @return Book
      */
     public function newBook($input = ['name' => 'test book', 'description' => 'My new test book']) {
-        return app(EntityRepo::class)->createFromInput('book', $input, false);
+        return app(BookRepo::class)->create($input);
     }
 
     /**
      * Create and return a new test chapter
      * @param array $input
      * @param Book $book
-     * @return \BookStack\Entities\Chapter
+     * @return Chapter
      */
     public function newChapter($input = ['name' => 'test chapter', 'description' => 'My new test chapter'], Book $book) {
-        return app(EntityRepo::class)->createFromInput('chapter', $input, $book);
+        return app(ChapterRepo::class)->create($input, $book);
     }
 
     /**
      * Create and return a new test page
      * @param array $input
      * @return Page
+     * @throws Throwable
      */
     public function newPage($input = ['name' => 'test page', 'html' => 'My new test page']) {
         $book = Book::first();
         $pageRepo = app(PageRepo::class);
-        $draftPage = $pageRepo->getDraftPage($book);
-        return $pageRepo->publishPageDraft($draftPage, $input);
+        $draftPage = $pageRepo->getNewDraftPage($book);
+        return $pageRepo->publishDraft($draftPage, $input);
     }
 
     /**
@@ -166,10 +174,10 @@ trait SharedTestHelpers
 
     /**
      * Give the given user some permissions.
-     * @param \BookStack\Auth\User $user
+     * @param User $user
      * @param array $permissions
      */
-    protected function giveUserPermissions(\BookStack\Auth\User $user, $permissions = [])
+    protected function giveUserPermissions(User $user, $permissions = [])
     {
         $newRole = $this->createNewRole($permissions);
         $user->attachRole($newRole);
@@ -197,11 +205,61 @@ trait SharedTestHelpers
      */
     protected function mockHttpFetch($returnData, int $times = 1)
     {
-        $mockHttp = \Mockery::mock(HttpFetcher::class);
+        $mockHttp = Mockery::mock(HttpFetcher::class);
         $this->app[HttpFetcher::class] = $mockHttp;
         $mockHttp->shouldReceive('fetch')
             ->times($times)
             ->andReturn($returnData);
     }
 
+    /**
+     * Run a set test with the given env variable.
+     * Remembers the original and resets the value after test.
+     * @param string $name
+     * @param $value
+     * @param callable $callback
+     */
+    protected function runWithEnv(string $name, $value, callable $callback)
+    {
+        Env::disablePutenv();
+        $originalVal = $_SERVER[$name] ?? null;
+
+        if (is_null($value)) {
+            unset($_SERVER[$name]);
+        } else {
+            $_SERVER[$name] = $value;
+        }
+
+        $this->refreshApplication();
+        $callback();
+
+        if (is_null($originalVal)) {
+            unset($_SERVER[$name]);
+        } else {
+            $_SERVER[$name] = $originalVal;
+        }
+    }
+
+    /**
+     * Check the keys and properties in the given map to include
+     * exist, albeit not exclusively, within the map to check.
+     * @param array $mapToInclude
+     * @param array $mapToCheck
+     * @param string $message
+     */
+    protected function assertArrayMapIncludes(array $mapToInclude, array $mapToCheck, string $message = '') : void
+    {
+        $passed = true;
+
+        foreach ($mapToInclude as $key => $value) {
+            if (!isset($mapToCheck[$key]) || $mapToCheck[$key] !== $mapToInclude[$key]) {
+                $passed = false;
+            }
+        }
+
+        $toIncludeStr = print_r($mapToInclude, true);
+        $toCheckStr = print_r($mapToCheck, true);
+        self::assertThat($passed, self::isTrue(), "Failed asserting that given map:\n\n{$toCheckStr}\n\nincludes:\n\n{$toIncludeStr}");
+    }
+
 }
\ No newline at end of file
diff --git a/tests/TestEmailTest.php b/tests/TestEmailTest.php
new file mode 100644 (file)
index 0000000..c06311d
--- /dev/null
@@ -0,0 +1,43 @@
+<?php namespace Tests;
+
+use BookStack\Notifications\TestEmail;
+use Illuminate\Support\Facades\Notification;
+
+class TestEmailTest extends TestCase
+{
+
+    public function test_a_send_test_button_shows()
+    {
+        $pageView = $this->asAdmin()->get('/settings/maintenance');
+        $formCssSelector = 'form[action$="/settings/maintenance/send-test-email"]';
+        $pageView->assertElementExists($formCssSelector);
+        $pageView->assertElementContains($formCssSelector . ' button', 'Send Test Email');
+    }
+
+    public function test_send_test_email_endpoint_sends_email_and_redirects_user_and_shows_notification()
+    {
+        Notification::fake();
+        $admin = $this->getAdmin();
+
+        $sendReq = $this->actingAs($admin)->post('/settings/maintenance/send-test-email');
+        $sendReq->assertRedirect('/settings/maintenance#image-cleanup');
+        $this->assertSessionHas('success', 'Email sent to ' . $admin->email);
+
+        Notification::assertSentTo($admin, TestEmail::class);
+    }
+
+    public function test_send_test_email_requires_settings_manage_permission()
+    {
+        Notification::fake();
+        $user = $this->getViewer();
+
+        $sendReq = $this->actingAs($user)->post('/settings/maintenance/send-test-email');
+        Notification::assertNothingSent();
+
+        $this->giveUserPermissions($user, ['settings-manage']);
+        $sendReq = $this->actingAs($user)->post('/settings/maintenance/send-test-email');
+        Notification::assertSentTo($user, TestEmail::class);
+    }
+
+
+}
\ No newline at end of file
diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php
new file mode 100644 (file)
index 0000000..51fdfe7
--- /dev/null
@@ -0,0 +1,43 @@
+<?php namespace Tests;
+
+use File;
+
+class ThemeTest extends TestCase
+{
+    protected $themeFolderName;
+    protected $themeFolderPath;
+
+    public function setUp(): void
+    {
+        parent::setUp();
+
+        // Create a folder and configure a theme
+        $this->themeFolderName = 'testing_theme_' . rtrim(base64_encode(time()), "=");
+        config()->set('view.theme', $this->themeFolderName);
+        $this->themeFolderPath = theme_path('');
+        File::makeDirectory($this->themeFolderPath);
+    }
+
+    public function tearDown(): void
+    {
+        // Cleanup the custom theme folder we created
+        File::deleteDirectory($this->themeFolderPath);
+
+        parent::tearDown();
+    }
+
+    public function test_translation_text_can_be_overriden_via_theme()
+    {
+        $translationPath = theme_path('/lang/en');
+        File::makeDirectory($translationPath, 0777, true);
+
+        $customTranslations = '<?php
+            return [\'books\' => \'Sandwiches\'];
+        ';
+        file_put_contents($translationPath . '/entities.php', $customTranslations);
+
+        $homeRequest = $this->actingAs($this->getViewer())->get('/');
+        $homeRequest->assertElementContains('header nav', 'Sandwiches');
+    }
+
+}
\ No newline at end of file
index 967915af97a8560ef7310fd86853aec01afbe2cf..c84305ad88112354a418f6e9c01a58cded82b9eb 100644 (file)
@@ -12,22 +12,18 @@ class ConfigTest extends TestCase
 
     public function test_filesystem_images_falls_back_to_storage_type_var()
     {
-        putenv('STORAGE_TYPE=local_secure');
-
-        $this->checkEnvConfigResult('STORAGE_IMAGE_TYPE', 's3', 'filesystems.images', 's3');
-        $this->checkEnvConfigResult('STORAGE_IMAGE_TYPE', null, 'filesystems.images', 'local_secure');
-
-        putenv('STORAGE_TYPE=local');
+        $this->runWithEnv('STORAGE_TYPE', 'local_secure', function() {
+            $this->checkEnvConfigResult('STORAGE_IMAGE_TYPE', 's3', 'filesystems.images', 's3');
+            $this->checkEnvConfigResult('STORAGE_IMAGE_TYPE', null, 'filesystems.images', 'local_secure');
+        });
     }
 
     public function test_filesystem_attachments_falls_back_to_storage_type_var()
     {
-        putenv('STORAGE_TYPE=local_secure');
-
-        $this->checkEnvConfigResult('STORAGE_ATTACHMENT_TYPE', 's3', 'filesystems.attachments', 's3');
-        $this->checkEnvConfigResult('STORAGE_ATTACHMENT_TYPE', null, 'filesystems.attachments', 'local_secure');
-
-        putenv('STORAGE_TYPE=local');
+        $this->runWithEnv('STORAGE_TYPE', 'local_secure', function() {
+            $this->checkEnvConfigResult('STORAGE_ATTACHMENT_TYPE', 's3', 'filesystems.attachments', 's3');
+            $this->checkEnvConfigResult('STORAGE_ATTACHMENT_TYPE', null, 'filesystems.attachments', 'local_secure');
+        });
     }
 
     public function test_app_url_blank_if_old_default_value()
@@ -49,12 +45,9 @@ class ConfigTest extends TestCase
      */
     protected function checkEnvConfigResult(string $envName, $envVal, string $configKey, string $expectedResult)
     {
-        $originalVal = getenv($envName);
-        $envString = $envName . (is_null($envVal) ? '' : '=') . ($envVal ?? '');
-        putenv($envString);
-        $this->refreshApplication();
-        $this->assertEquals($expectedResult, config($configKey));
-        putenv($envString = $envName . (empty($originalVal) ? '' : '=') . ($originalVal ?? ''));
+        $this->runWithEnv($envName, $envVal, function() use ($configKey, $expectedResult) {
+            $this->assertEquals($expectedResult, config($configKey));
+        });
     }
 
 }
\ No newline at end of file
diff --git a/tests/Unit/PageRepoTest.php b/tests/Unit/PageRepoTest.php
deleted file mode 100644 (file)
index 41e7c2f..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-namespace Tests;
-
-use BookStack\Entities\Repos\PageRepo;
-
-class PageRepoTest extends TestCase
-{
-    /**
-     * @var PageRepo $pageRepo
-     */
-    protected $pageRepo;
-
-    protected function setUp()
-    {
-        parent::setUp();
-        $this->pageRepo = app()->make(PageRepo::class);
-    }
-
-    public function test_get_page_nav_sets_correct_properties()
-    {
-        $content = '<h1 id="testa">Hello</h1><h2 id="testb">There</h2><h3 id="testc">Donkey</h3>';
-        $navMap = $this->pageRepo->getPageNav($content);
-
-        $this->assertCount(3, $navMap);
-        $this->assertArraySubset([
-            'nodeName' => 'h1',
-            'link' => '#testa',
-            'text' => 'Hello',
-            'level' => 1,
-        ], $navMap[0]);
-        $this->assertArraySubset([
-            'nodeName' => 'h2',
-            'link' => '#testb',
-            'text' => 'There',
-            'level' => 2,
-        ], $navMap[1]);
-        $this->assertArraySubset([
-            'nodeName' => 'h3',
-            'link' => '#testc',
-            'text' => 'Donkey',
-            'level' => 3,
-        ], $navMap[2]);
-    }
-
-    public function test_get_page_nav_does_not_show_empty_titles()
-    {
-        $content = '<h1 id="testa">Hello</h1><h2 id="testb">&nbsp;</h2><h3 id="testc"></h3>';
-        $navMap = $this->pageRepo->getPageNav($content);
-
-        $this->assertCount(1, $navMap);
-        $this->assertArraySubset([
-            'nodeName' => 'h1',
-            'link' => '#testa',
-            'text' => 'Hello'
-        ], $navMap[0]);
-    }
-
-    public function test_get_page_nav_shifts_headers_if_only_smaller_ones_are_used()
-    {
-        $content = '<h4 id="testa">Hello</h4><h5 id="testb">There</h5><h6 id="testc">Donkey</h6>';
-        $navMap = $this->pageRepo->getPageNav($content);
-
-        $this->assertCount(3, $navMap);
-        $this->assertArraySubset([
-            'nodeName' => 'h4',
-            'level' => 1,
-        ], $navMap[0]);
-        $this->assertArraySubset([
-            'nodeName' => 'h5',
-            'level' => 2,
-        ], $navMap[1]);
-        $this->assertArraySubset([
-            'nodeName' => 'h6',
-            'level' => 3,
-        ], $navMap[2]);
-    }
-
-}
\ No newline at end of file
index c7d33312c4c0dccc2ca5f2e3f421cb0941703d24..c2386443c6fdacd9260cc8b5bfb7777c74a5c208 100644 (file)
@@ -16,10 +16,16 @@ class UrlTest extends TestCase
 
     public function test_url_helper_takes_custom_url_into_account()
     {
-        putenv('APP_URL=http://example.com/bookstack');
-        $this->refreshApplication();
-        $this->assertEquals('http://example.com/bookstack/books', url('/books'));
-        putenv('APP_URL=');
+        $this->runWithEnv('APP_URL', 'http://example.com/bookstack', function() {
+            $this->assertEquals('http://example.com/bookstack/books', url('/books'));
+        });
+    }
+
+    public function test_url_helper_sets_correct_scheme_even_when_request_scheme_is_different()
+    {
+        $this->runWithEnv('APP_URL', 'https://example.com/', function() {
+            $this->get('http://example.com/login')->assertSee('https://example.com/dist/styles.css');
+        });
     }
 
 }
\ No newline at end of file
index 35ffda821ef35262f1b135fcde4c8cbc90c7a519..12b254d00a4c84ac87877d8e3e49917163f7c071 100644 (file)
@@ -78,7 +78,7 @@ class AttachmentTest extends TestCase
         $upload->assertStatus(200);
 
         $attachment = Attachment::query()->orderBy('id', 'desc')->first();
-        $this->assertNotContains($fileName, $attachment->path);
+        $this->assertStringNotContainsString($fileName, $attachment->path);
         $this->assertStringEndsWith('.txt', $attachment->path);
     }
 
@@ -223,7 +223,7 @@ class AttachmentTest extends TestCase
     {
         $admin = $this->getAdmin();
         $viewer = $this->getViewer();
-        $page = Page::first();
+        $page = Page::first(); /** @var Page $page */
 
         $this->actingAs($admin);
         $fileName = 'permission_test.txt';
@@ -233,7 +233,7 @@ class AttachmentTest extends TestCase
         $page->restricted = true;
         $page->permissions()->delete();
         $page->save();
-        $this->app[PermissionService::class]->buildJointPermissionsForEntity($page);
+        $page->rebuildPermissions();
         $page->load('jointPermissions');
 
         $this->actingAs($viewer);
index f9265337889c94ef7f049159e92da6bb3aada780..0615a95ce52641cff442ce913d59bb65f812d2b3 100644 (file)
@@ -4,6 +4,7 @@ use BookStack\Entities\Repos\PageRepo;
 use BookStack\Uploads\Image;
 use BookStack\Entities\Page;
 use BookStack\Uploads\ImageService;
+use Illuminate\Support\Str;
 use Tests\TestCase;
 
 class ImageTest extends TestCase
@@ -43,7 +44,7 @@ class ImageTest extends TestCase
         $imgDetails = $this->uploadGalleryImage();
         $image = Image::query()->first();
 
-        $newName = str_random();
+        $newName = Str::random();
         $update = $this->put('/images/' . $image->id, ['name' => $newName]);
         $update->assertSuccessful();
         $update->assertJson([
@@ -89,7 +90,7 @@ class ImageTest extends TestCase
         $searchHitRequest = $this->get("/images/gallery?page=1&uploaded_to={$pageId}&search={$namePartial}");
         $searchHitRequest->assertSuccessful()->assertJson($resultJson);
 
-        $namePartial = str_random(16);
+        $namePartial = Str::random(16);
         $searchHitRequest = $this->get("/images/gallery?page=1&uploaded_to={$pageId}&search={$namePartial}");
         $searchHitRequest->assertSuccessful()->assertExactJson($emptyJson);
     }
@@ -208,7 +209,7 @@ class ImageTest extends TestCase
 
         $encodedImageContent = base64_encode(file_get_contents($expectedPath));
         $export = $this->get($page->getUrl('/export/html'));
-        $this->assertTrue(str_contains($export->getContent(), $encodedImageContent), 'Uploaded image in export content');
+        $this->assertTrue(strpos($export->getContent(), $encodedImageContent) !== false, 'Uploaded image in export content');
 
         if (file_exists($expectedPath)) {
             unlink($expectedPath);
@@ -366,7 +367,7 @@ class ImageTest extends TestCase
         $image = Image::where('type', '=', 'gallery')->first();
 
         $pageRepo = app(PageRepo::class);
-        $pageRepo->updatePage($page, $page->book_id, [
+        $pageRepo->update($page, [
             'name' => $page->name,
             'html' => $page->html . "<img src=\"{$image->url}\">",
             'summary' => ''
@@ -378,7 +379,7 @@ class ImageTest extends TestCase
         $this->assertCount(0, $toDelete);
 
         // Save a revision of our page without the image;
-        $pageRepo->updatePage($page, $page->book_id, [
+        $pageRepo->update($page, [
             'name' => $page->name,
             'html' => "<p>Hello</p>",
             'summary' => ''
index a7c7505a80db83867bdfb9c1636653589ccbe46a..fc1a529ae43076f59d38d2d642c534c0ff816cd7 100644 (file)
@@ -4,7 +4,7 @@ class UserProfileTest extends BrowserKitTest
 {
     protected $user;
 
-    public function setUp()
+    public function setUp(): void
     {
         parent::setUp();
         $this->user = \BookStack\Auth\User::all()->last();
diff --git a/version b/version
index 4494802a4fd0dc42714e3068afaba8ef2e351a31..d400403770b07fdcd3eb850c6b28c62b7c1a5d83 100644 (file)
--- a/version
+++ b/version
@@ -1 +1 @@
-v0.27-dev
+v0.28-dev
index 78b679a0a82f51fab622fb35ff60c0dece578119..e496340c46c63a4409d3b51702a3de98c54f7459 100644 (file)
@@ -7,10 +7,10 @@ const config = {
     target: 'web',
     mode: dev? 'development' : 'production',
     entry: {
-        app: './resources/assets/js/index.js',
-        styles: './resources/assets/sass/styles.scss',
-        "export-styles": './resources/assets/sass/export-styles.scss',
-        "print-styles": './resources/assets/sass/print-styles.scss',
+        app: './resources/js/index.js',
+        styles: './resources/sass/styles.scss',
+        "export-styles": './resources/sass/export-styles.scss',
+        "print-styles": './resources/sass/print-styles.scss',
     },
     output: {
         filename: '[name].js',